Skip to content

Commit 70680b1

Browse files
Add crypto-square exercise (#49)
1 parent a8cdbd2 commit 70680b1

File tree

14 files changed

+570
-0
lines changed

14 files changed

+570
-0
lines changed

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,14 @@
297297
"prerequisites": [],
298298
"difficulty": 4
299299
},
300+
{
301+
"slug": "crypto-square",
302+
"name": "Crypto Square",
303+
"uuid": "15a4f6f9-be4b-48bb-927d-fe337c5434b6",
304+
"practices": [],
305+
"prerequisites": [],
306+
"difficulty": 5
307+
},
300308
{
301309
"slug": "nth-prime",
302310
"name": "Nth Prime",
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Instructions
2+
3+
Implement the classic method for composing secret messages called a square code.
4+
5+
Given an English text, output the encoded version of that text.
6+
7+
First, the input is normalized: the spaces and punctuation are removed from the English text and the message is down-cased.
8+
9+
Then, the normalized characters are broken into rows.
10+
These rows can be regarded as forming a rectangle when printed with intervening newlines.
11+
12+
For example, the sentence
13+
14+
```text
15+
"If man was meant to stay on the ground, god would have given us roots."
16+
```
17+
18+
is normalized to:
19+
20+
```text
21+
"ifmanwasmeanttostayonthegroundgodwouldhavegivenusroots"
22+
```
23+
24+
The plaintext should be organized into a rectangle as square as possible.
25+
The size of the rectangle should be decided by the length of the message.
26+
27+
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:
28+
29+
- `r * c >= length of message`,
30+
- and `c >= r`,
31+
- and `c - r <= 1`.
32+
33+
Our normalized text is 54 characters long, dictating a rectangle with `c = 8` and `r = 7`:
34+
35+
```text
36+
"ifmanwas"
37+
"meanttos"
38+
"tayonthe"
39+
"groundgo"
40+
"dwouldha"
41+
"vegivenu"
42+
"sroots "
43+
```
44+
45+
The coded message is obtained by reading down the columns going left to right.
46+
47+
The message above is coded as:
48+
49+
```text
50+
"imtgdvsfearwermayoogoanouuiontnnlvtwttddesaohghnsseoau"
51+
```
52+
53+
Output the encoded text in chunks that fill perfect rectangles `(r X c)`, with `c` chunks of `r` length, separated by spaces.
54+
For phrases that are `n` characters short of the perfect rectangle, pad each of the last `n` chunks with a single trailing space.
55+
56+
```text
57+
"imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau "
58+
```
59+
60+
Notice that were we to stack these, we could visually decode the ciphertext back in to the original message:
61+
62+
```text
63+
"imtgdvs"
64+
"fearwer"
65+
"mayoogo"
66+
"anouuio"
67+
"ntnnlvt"
68+
"wttddes"
69+
"aohghn "
70+
"sseoau "
71+
```
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace CryptoSquare
2+
3+
def countAlphanum (plaintext : String) : Nat := Id.run do
4+
let mut count := 0
5+
for c in plaintext.toList do
6+
if Char.isAlphanum c then
7+
count := count + 1
8+
return count
9+
10+
def squareRoot (radicand : Nat) : Nat := Id.run do
11+
for guess in [0:radicand] do
12+
if guess * guess >= radicand then return guess
13+
return radicand
14+
15+
def ciphertext (plaintext : String) : String :=
16+
let length := countAlphanum plaintext
17+
if length == 0 then ""
18+
else Id.run do
19+
let columns := squareRoot length
20+
let rows := (length + columns - 1) / columns
21+
let mut chars := Array.replicate ((rows * columns) + columns - 1) ' '
22+
let mut count := 0
23+
for c in (String.toLower plaintext).toList do
24+
if Char.isAlphanum c then
25+
let index := (count % columns) * (rows + 1) + (count / columns)
26+
chars := chars.set! index c
27+
count := count + 1
28+
return chars.toList.asString
29+
30+
end CryptoSquare
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"authors": [
3+
"keiravillekode"
4+
],
5+
"files": {
6+
"solution": [
7+
"CryptoSquare.lean"
8+
],
9+
"test": [
10+
"CryptoSquareTest.lean"
11+
],
12+
"example": [
13+
".meta/Example.lean"
14+
]
15+
},
16+
"blurb": "Implement the classic method for composing secret messages called a square code.",
17+
"source": "J Dalbey's Programming Practice problems",
18+
"source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html"
19+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[407c3837-9aa7-4111-ab63-ec54b58e8e9f]
13+
description = "empty plaintext results in an empty ciphertext"
14+
15+
[aad04a25-b8bb-4304-888b-581bea8e0040]
16+
description = "normalization results in empty plaintext"
17+
18+
[64131d65-6fd9-4f58-bdd8-4a2370fb481d]
19+
description = "Lowercase"
20+
21+
[63a4b0ed-1e3c-41ea-a999-f6f26ba447d6]
22+
description = "Remove spaces"
23+
24+
[1b5348a1-7893-44c1-8197-42d48d18756c]
25+
description = "Remove punctuation"
26+
27+
[8574a1d3-4a08-4cec-a7c7-de93a164f41a]
28+
description = "9 character plaintext results in 3 chunks of 3 characters"
29+
30+
[a65d3fa1-9e09-43f9-bcec-7a672aec3eae]
31+
description = "8 character plaintext results in 3 chunks, the last one with a trailing space"
32+
33+
[fbcb0c6d-4c39-4a31-83f6-c473baa6af80]
34+
description = "54 character plaintext results in 7 chunks, the last two with trailing spaces"
35+
include = false
36+
37+
[33fd914e-fa44-445b-8f38-ff8fbc9fe6e6]
38+
description = "54 character plaintext results in 8 chunks, the last two with trailing spaces"
39+
reimplements = "fbcb0c6d-4c39-4a31-83f6-c473baa6af80"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace CryptoSquare
2+
3+
def ciphertext (plaintext : String) : String :=
4+
sorry
5+
6+
end CryptoSquare
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import LeanTest
2+
import CryptoSquare
3+
4+
open LeanTest
5+
6+
def cryptoSquareTests : TestSuite :=
7+
(TestSuite.empty "CryptoSquare")
8+
|>.addTest "empty plaintext results in an empty ciphertext" (do
9+
return assertEqual "" (CryptoSquare.ciphertext ""))
10+
|>.addTest "normalization results in empty plaintext" (do
11+
return assertEqual "" (CryptoSquare.ciphertext "... --- ..."))
12+
|>.addTest "Lowercase" (do
13+
return assertEqual "a" (CryptoSquare.ciphertext "A"))
14+
|>.addTest "Remove spaces" (do
15+
return assertEqual "b" (CryptoSquare.ciphertext " b "))
16+
|>.addTest "Remove punctuation" (do
17+
return assertEqual "1" (CryptoSquare.ciphertext "@1,%!"))
18+
|>.addTest "9 character plaintext results in 3 chunks of 3 characters" (do
19+
return assertEqual "tsf hiu isn" (CryptoSquare.ciphertext "This is fun!"))
20+
|>.addTest "8 character plaintext results in 3 chunks, the last one with a trailing space" (do
21+
return assertEqual "clu hlt io " (CryptoSquare.ciphertext "Chill out."))
22+
|>.addTest "54 character plaintext results in 8 chunks, the last two with trailing spaces" (do
23+
return assertEqual "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau " (CryptoSquare.ciphertext "If man was meant to stay on the ground, god would have given us roots."))
24+
25+
def main : IO UInt32 := do
26+
runTestSuitesWithExitCode [cryptoSquareTests]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name = "crypto-square"
2+
version = "0.1.0"
3+
defaultTargets = ["CryptoSquareTest"]
4+
testDriver = "CryptoSquareTest"
5+
6+
[[lean_lib]]
7+
name = "LeanTest"
8+
srcDir = "vendor/LeanTest"
9+
10+
[[lean_lib]]
11+
name = "CryptoSquare"
12+
13+
[[lean_exe]]
14+
name = "CryptoSquareTest"
15+
root = "CryptoSquareTest"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
leanprover/lean4:v4.25.2
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- This module serves as the root of the `LeanTest` library.
2+
-- Import modules here that should be built as part of the library.
3+
import LeanTest.Assertions
4+
import LeanTest.Test

0 commit comments

Comments
 (0)