ðŸŽ‰ Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io ðŸŽ‰

# nilvon9wo's solution

## to Crypto Square in the Elixir Track

Published at Jun 05 2020 · 0 comments
Instructions
Test suite
Solution

Implement the classic method for composing secret messages called a square code.

Given an English text, output the encoded version of that text.

First, the input is normalized: the spaces and punctuation are removed from the English text and the message is downcased.

Then, the normalized characters are broken into rows. These rows can be regarded as forming a rectangle when printed with intervening newlines.

For example, the sentence

``````"If man was meant to stay on the ground, god would have given us roots."
``````

is normalized to:

``````"ifmanwasmeanttostayonthegroundgodwouldhavegivenusroots"
``````

The plaintext should be organized in to a rectangle. The size of the rectangle (`r x c`) should be decided by the length of the message, such that `c >= r` and `c - r <= 1`, where `c` is the number of columns and `r` is the number of rows.

Our normalized text is 54 characters long, dictating a rectangle with `c = 8` and `r = 7`:

``````"ifmanwas"
"meanttos"
"tayonthe"
"groundgo"
"dwouldha"
"vegivenu"
"sroots  "
``````

The coded message is obtained by reading down the columns going left to right.

The message above is coded as:

``````"imtgdvsfearwermayoogoanouuiontnnlvtwttddesaohghnsseoau"
``````

Output the encoded text in chunks that fill perfect rectangles `(r X c)`, with `c` chunks of `r` length, separated by spaces. For phrases that are `n` characters short of the perfect rectangle, pad each of the last `n` chunks with a single trailing space.

``````"imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn  sseoau "
``````

Notice that were we to stack these, we could visually decode the cyphertext back in to the original message:

``````"imtgdvs"
"fearwer"
"mayoogo"
"anouuio"
"ntnnlvt"
"wttddes"
"aohghn "
"sseoau "
``````

## Running tests

Execute the tests with:

``````\$ mix test
``````

### Pending tests

In the test suites, all but the first test have been skipped.

Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol.

For example:

``````# @tag :pending
test "shouting" do
assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
end
``````

Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the test suite.

``````# ExUnit.configure exclude: :pending, trace: true
``````

If you're stuck on something, it may help to look at some of the available resources out there where answers might be found.

## Source

J Dalbey's Programming Practice problems http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html

## Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

### crypto_square_test.exs

``````defmodule CryptoSquareTest do
use ExUnit.Case

# @tag :pending
test "empty string" do
assert CryptoSquare.encode("") == ""
end

@tag :pending
test "perfect square" do
assert CryptoSquare.encode("abcd") == "ac bd"
end

@tag :pending
test "uppercase string" do
assert CryptoSquare.encode("ABCD") == "ac bd"
end

@tag :pending
test "small imperfect square" do
assert CryptoSquare.encode("This is easy") == "tis hsy ie sa"
end

@tag :pending
test "punctuation and numbers" do
assert CryptoSquare.encode("1, 2, 3, Go! Go, for God's sake!") == "1gga 2ook 3fde gos ors"
end

@tag :pending
test "long string" do
msg = "If man was meant to stay on the ground, god would have given us roots."
cipher = "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau"
assert CryptoSquare.encode(msg) == cipher
end
end``````

### test_helper.exs

``````ExUnit.start()
ExUnit.configure(exclude: :pending, trace: true)``````
``````defmodule CryptoSquare do
@doc """
Encode string square methods
## Examples

iex> CryptoSquare.encode("abcd")
"ac bd"
"""
@spec encode(String.t()) :: String.t()
def encode("" = _plain_text),
do: ""

def encode(plain_text),
do:
plain_text
|> normalize()
|> to_rectangle()
|> Enum.reverse()
|> Enum.map(&String.graphemes/1)
|> Enum.zip()
|> Enum.map(&Tuple.to_list/1)
|> Enum.map(&Enum.join/1)
|> Enum.map(&String.trim/1)
|> Enum.join(" ")

defp normalize(plain_text),
do:
plain_text
|> String.downcase()
|> String.replace(~r/[^a-z0-9]/, "")

defp to_rectangle(normalized_text),
do:
normalized_text
|> calculate_dimensions()
|> make_rows(normalized_text)

defp calculate_dimensions(normalized_text) do
text_length = String.length(normalized_text)
square_root = :math.sqrt(text_length)
truncated_root = trunc(square_root)

columns =
if square_root == truncated_root,
do: truncated_root,
else: truncated_root + 1

if :math.pow(columns, 2) >= text_length,
do: columns,
else: columns + 1
end

defp make_rows(columns, text, accumulator \\ []) do
if String.length(text) < columns,
do: [String.pad_trailing(text, columns) | accumulator],
else:
make_rows(
columns,
String.slice(text, columns, String.length(text) - columns),
[String.slice(text, 0, columns) | accumulator]
)
end
end``````