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

# groentesoep's solution

## to Crypto Square in the Elixir Track

Published at Dec 23 2019 · 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
str
|> String.downcase()
|> String.replace(~r/[\s\.\,\!\`\']/, "")
|> String.codepoints()
|> create_rectangle()
|> List.zip()
|> Enum.map(&Tuple.to_list/1)
|> Enum.join(" ")
end

defp create_rectangle(str) do
col = calculate_columns(str)
Enum.chunk_every(str, col, col, Stream.cycle([""]))
end

defp calculate_columns(str) do
length(str) |> :math.sqrt() |> Float.ceil |> trunc()
end
end