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

# liquidsolid's solution

## to Crypto Square in the Elixir Track

Published at May 03 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(str) do
chars = Regex.scan(~r"[a-z0-9]"i, str)
|> Enum.map(fn [x] -> x end)
|> Enum.map(&String.downcase/1)

if length(chars) === 0 do
""
else
Enum.chunk_every(chars, chars |> length() |> :math.sqrt() |> ceil())
|> translate()
|> Enum.join(" ")
end
end

defp translate([]), do: []
defp translate(chunks) do
{chunks, token} = Enum.map_reduce(chunks, [], fn [h | t], list ->
{t, list ++ [h]}
end)
chunks = Enum.filter(chunks, fn x -> length(x) > 0 end)
[token |> Enum.join("")] ++ translate(chunks)
end
end``````