Skip to content

Commit

Permalink
Add crypto-square exercise (#153)
Browse files Browse the repository at this point in the history
* Add crypto-square exercise

* Tests have input and expectation on separate lines

* Omit return keyword in single line functions
  • Loading branch information
keiravillekode authored Nov 10, 2023
1 parent 7d9bb0c commit 6f4d704
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 0 deletions.
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,14 @@
"prerequisites": [],
"difficulty": 4
},
{
"slug": "crypto-square",
"name": "Crypto Square",
"uuid": "0a9050d7-01dc-4c69-8f85-72d8c19b876a",
"practices": [],
"prerequisites": [],
"difficulty": 4
},
{
"slug": "spiral-matrix",
"name": "Spiral Matrix",
Expand Down
71 changes: 71 additions & 0 deletions exercises/practice/crypto-square/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Instructions

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 down-cased.

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

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

is normalized to:

```text
"ifmanwasmeanttostayonthegroundgodwouldhavegivenusroots"
```

The plaintext should be organized into a rectangle as square as possible.
The size of the rectangle should be decided by the length of the message.

If `c` is the number of columns and `r` is the number of rows, then for the rectangle `r` x `c` find the smallest possible integer `c` such that:

- `r * c >= length of message`,
- and `c >= r`,
- and `c - r <= 1`.

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

```text
"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:

```text
"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.

```text
"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:

```text
"imtgdvs"
"fearwer"
"mayoogo"
"anouuio"
"ntnnlvt"
"wttddes"
"aohghn "
"sseoau "
```
19 changes: 19 additions & 0 deletions exercises/practice/crypto-square/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"keiravillekode"
],
"files": {
"solution": [
"crypto-square.wren"
],
"test": [
"crypto-square.spec.wren"
],
"example": [
".meta/proof.ci.wren"
]
},
"blurb": "Implement the classic method for composing secret messages called a square code.",
"source": "J Dalbey's Programming Practice problems",
"source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html"
}
48 changes: 48 additions & 0 deletions exercises/practice/crypto-square/.meta/proof.ci.wren
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
class CryptoSquare {
static columns(n) {
var c = 0
while (c * c < n) {
c = c + 1
}
return c
}

static rows(n, c) {
var r = c - 1
if (r * c < n) {
r = r + 1
}
// r * c >= n
return r
}

static ciphertext(plaintext) {
var toLower = Fn.new {|codePoint| ((65..90).contains(codePoint)) ? codePoint + 32 : codePoint }

var isAlphanumeric = Fn.new {|codePoint| (48..57).contains(codePoint) || (65..90).contains(codePoint) || (97..122).contains(codePoint) }

var toString = Fn.new {|codePoint| String.fromCodePoint(codePoint) }

var codePoints = plaintext.codePoints.map(toLower).where(isAlphanumeric).toList

var n = codePoints.count
if (n <= 1) {
return codePoints.map(toString).join()
}

var c = columns(n)
var r = rows(n, c)
var buffer = List.filled((r + 1) * c - 1, 32)
var index = 0
for (i in 0...r) {
for (j in 0...c) {
buffer[j * (r + 1) + i] = codePoints[index]
index = index + 1
if (index == n) {
break
}
}
}
return buffer.map(toString).join()
}
}
34 changes: 34 additions & 0 deletions exercises/practice/crypto-square/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[407c3837-9aa7-4111-ab63-ec54b58e8e9f]
description = "empty plaintext results in an empty ciphertext"

[aad04a25-b8bb-4304-888b-581bea8e0040]
description = "normalization results in empty plaintext"

[64131d65-6fd9-4f58-bdd8-4a2370fb481d]
description = "Lowercase"

[63a4b0ed-1e3c-41ea-a999-f6f26ba447d6]
description = "Remove spaces"

[1b5348a1-7893-44c1-8197-42d48d18756c]
description = "Remove punctuation"

[8574a1d3-4a08-4cec-a7c7-de93a164f41a]
description = "9 character plaintext results in 3 chunks of 3 characters"

[a65d3fa1-9e09-43f9-bcec-7a672aec3eae]
description = "8 character plaintext results in 3 chunks, the last one with a trailing space"

[fbcb0c6d-4c39-4a31-83f6-c473baa6af80]
description = "54 character plaintext results in 7 chunks, the last two with trailing spaces"
21 changes: 21 additions & 0 deletions exercises/practice/crypto-square/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Exercism

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
52 changes: 52 additions & 0 deletions exercises/practice/crypto-square/crypto-square.spec.wren
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import "./crypto-square" for CryptoSquare
import "wren-testie/testie" for Testie, Expect

Testie.test("CryptoSquare.ciphertext()") { |do, skip|
do.test("empty plaintext results in an empty ciphertext") {
var phrase = ""
var expect = ""
Expect.value(CryptoSquare.ciphertext(phrase)).toEqual(expect)
}

skip.test("normalization results in empty plaintext") {
var phrase = "... --- ..."
var expect = ""
Expect.value(CryptoSquare.ciphertext(phrase)).toEqual(expect)
}

skip.test("Lowercase") {
var phrase = "A"
var expect = "a"
Expect.value(CryptoSquare.ciphertext(phrase)).toEqual(expect)
}

skip.test("Remove spaces") {
var phrase = " b "
var expect = "b"
Expect.value(CryptoSquare.ciphertext(phrase)).toEqual(expect)
}

skip.test("Remove punctuation") {
var phrase = "@1,\%!"
var expect = "1"
Expect.value(CryptoSquare.ciphertext(phrase)).toEqual(expect)
}

skip.test("9 character plaintext results in 3 chunks of 3 characters") {
var phrase = "This is fun!"
var expect = "tsf hiu isn"
Expect.value(CryptoSquare.ciphertext(phrase)).toEqual(expect)
}

skip.test("8 character plaintext results in 3 chunks, the last one with a trailing space") {
var phrase = "Chill out."
var expect = "clu hlt io "
Expect.value(CryptoSquare.ciphertext(phrase)).toEqual(expect)
}

skip.test("54 character plaintext results in 7 chunks, the last two with trailing spaces") {
var phrase = "If man was meant to stay on the ground, god would have given us roots."
var expect = "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau "
Expect.value(CryptoSquare.ciphertext(phrase)).toEqual(expect)
}
}
5 changes: 5 additions & 0 deletions exercises/practice/crypto-square/crypto-square.wren
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class CryptoSquare {
static ciphertext(plaintext) {
Fiber.abort("Remove this statement and implement this function")
}
}
14 changes: 14 additions & 0 deletions exercises/practice/crypto-square/package.wren
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import "wren-package" for WrenPackage, Dependency
import "os" for Process

class Package is WrenPackage {
construct new() {}
name { "exercism/crypto-square" }
dependencies {
return [
Dependency.new("wren-testie", "0.3.0", "https://github.com/joshgoebel/wren-testie.git")
]
}
}

Package.new().default()

0 comments on commit 6f4d704

Please sign in to comment.