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

# mcberros's solution

## to Crypto Square in the Elixir Track

Published at May 15 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(""), do: ""

def encode(str) do
normalized = normalize(str)
{rows, columns} = get_number_rows_columns(normalized)
slice_by_row(normalized, rows, columns)
|> join_by_columns(columns)
end

defp normalize(str) do
String.replace(str, ~r/[!@#\$%^&*()=_+|;':",.<>' ?]/, "") |> String.downcase
end

defp get_number_rows_columns(str) do
length = str |> String.length
rows = trunc(length |> :math.sqrt())
columns = round(length / rows)
columns = if (rows * columns < length), do: columns + (length - (rows * columns)), else: columns
{rows, columns}
end

defp slice_by_row(normalized, rows, columns) do
Enum.map(0..rows - 1, fn r -> String.slice(normalized, r * columns, columns) end)
end

defp join_by_columns(sliced, columns) do
Enum.map_join(0..columns-1, " ", fn c -> Enum.reduce(sliced, "", fn s, acc -> acc <> String.slice(s, c, 1) end) end)
end
end``````