🎉 Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io 🎉
Avatar of --

--'s solution

to Crypto Square in the Ruby Track

Published at Sep 09 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 ciphertext back in to the original message:

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

For installation and learning resources, refer to the Ruby resources page.

For running the tests provided, you will need the Minitest gem. Open a terminal window and run the following command to install minitest:

gem install minitest

If you would like color output, you can require 'minitest/pride' in the test file, or note the alternative instruction, below, for running the test file.

Run the tests from the exercise directory using the following command:

ruby crypto_square_test.rb

To include color from the command line:

ruby -r minitest/pride crypto_square_test.rb

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.rb

require 'minitest/autorun'
require_relative 'crypto_square'

# Common test data version: 3.2.0 e00dfb3
class CryptoSquareTest < Minitest::Test
  def test_empty_plaintext_results_in_an_empty_ciphertext
    # skip
    plaintext = ''
    assert_equal "", Crypto.new(plaintext).ciphertext
  end

  def test_lowercase
    skip
    plaintext = 'A'
    assert_equal "a", Crypto.new(plaintext).ciphertext
  end

  def test_remove_spaces
    skip
    plaintext = '  b '
    assert_equal "b", Crypto.new(plaintext).ciphertext
  end

  def test_remove_punctuation
    skip
    plaintext = '@1,%!'
    assert_equal "1", Crypto.new(plaintext).ciphertext
  end

  def test_9_character_plaintext_results_in_3_chunks_of_3_characters
    skip
    plaintext = 'This is fun!'
    assert_equal "tsf hiu isn", Crypto.new(plaintext).ciphertext
  end

  def test_8_character_plaintext_results_in_3_chunks_the_last_one_with_a_trailing_space
    skip
    plaintext = 'Chill out.'
    assert_equal "clu hlt io ", Crypto.new(plaintext).ciphertext
  end

  def test_54_character_plaintext_results_in_7_chunks_the_last_two_with_trailing_spaces
    skip
    plaintext = 'If man was meant to stay on the ground, god would have given us roots.'
    assert_equal "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn  sseoau ", Crypto.new(plaintext).ciphertext
  end
end
class Crypto
  def initialize plaintext
    @plaintext = plaintext
  end

  def ciphertext
    return '' if @plaintext.length == 0

    grid.transpose.collect(&:join).join(' ')
  end

  private

  def grid
    padded.chars.each_slice(row_size).to_a
  end

  def padded
    "#{normalized}#{' ' * padding_length}"
  end

  def padding_length
    (row_size - normalized.length % row_size) % row_size
  end

  def normalized
    @normalized ||= @plaintext.downcase.gsub /\W/, ''
  end

  def row_size
    Math.sqrt(normalized.length).ceil
  end
end

Community comments

Find this solution interesting? Ask the author a question to learn more.

--'s Reflection

Array#in_groups_of found in ActiveSupport (any Rails app) is a wrapper around Enumerable#each_slice which provides the padding functionality as an argument. Using that would simplify the code just slightly but would also add a good size dependency so it's a trade-off.