diff --git a/exercises/practice/crypto-square/.meta/Cargo-example.toml b/exercises/practice/crypto-square/.meta/Cargo-example.toml deleted file mode 100644 index 5c7303093..000000000 --- a/exercises/practice/crypto-square/.meta/Cargo-example.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -edition = "2021" -name = "crypto-square" -version = "0.1.0" - -[dependencies] -itertools = "0.6.1" diff --git a/exercises/practice/crypto-square/.meta/example.rs b/exercises/practice/crypto-square/.meta/example.rs index 5a7619006..5d5feb4b0 100644 --- a/exercises/practice/crypto-square/.meta/example.rs +++ b/exercises/practice/crypto-square/.meta/example.rs @@ -1,117 +1,40 @@ -extern crate itertools; -use itertools::Itertools; - -/// Encrypt the input string using square cryptography pub fn encrypt(input: &str) -> String { - let prepared = prepare(input); - if prepared.is_empty() { + let mut input: Vec<_> = input + .to_ascii_lowercase() + .chars() + .filter(char::is_ascii_alphanumeric) + .collect(); + if input.is_empty() { return String::new(); } - let (cols, rows) = dimensions(prepared.len()); - - let mut output = String::with_capacity(input.len()); - for chunk_iterator in SquareIndexer::new(rows, cols).chunks(cols).into_iter() { - for ch_idx in chunk_iterator { - if ch_idx < prepared.len() { - output.push(prepared[ch_idx]); - } - } - output.push(' '); - } - - // we know there's one extra space at the end - output.pop(); - - output -} - -/// Construct a vector of characters from the given input. -/// -/// Constrain it to the allowed chars: lowercase ascii letters. -/// We construct a vector here because the length of the input -/// matters when constructing the output, so we need to know -/// how many input chars there are. We could treat it as a stream -/// and just stream twice, but collecting it into a vector works -/// equally well and might be a bit faster. -fn prepare(input: &str) -> Vec { - let mut output = Vec::with_capacity(input.len()); - - output.extend( - input - .chars() - .filter(|&c| c.is_ascii() && !c.is_whitespace() && !c.is_ascii_punctuation()) - .map(|c| c.to_ascii_lowercase()), - ); - - // add space padding to the end such that the actual string returned - // forms a perfect rectangle - let (r, c) = dimensions(output.len()); - output.resize(r * c, ' '); + let width = (input.len() as f64).sqrt().ceil() as usize; + let size = width * width; - output.shrink_to_fit(); - - output -} - -/// Get the dimensions of the appropriate bounding rectangle for this encryption -/// -/// To find `(rows, cols)` such that `cols >= rows && cols - rows <= 1`, we find -/// the least square greater than or equal to the message length. Its square root -/// is the cols. If the message length is a perfect square, `rows` is the same. -/// Otherwise, it is one less. -fn dimensions(length: usize) -> (usize, usize) { - let cols = (length as f64).sqrt().ceil() as usize; - let rows = if cols * cols == length { - cols + // skip last row if already empty + let last_row = if input.len() + width > size { + width } else { - cols - 1 + width - 1 }; - (rows, cols) -} -/// Iterator over the indices of the appropriate chars of the output. -/// -/// For a (2, 3) (r, c) grid, yields (0, 3, 1, 4, 2, 5). -/// Does no bounds checking or space insertion: that's handled elsewhere. -#[derive(Debug)] -struct SquareIndexer { - rows: usize, - cols: usize, - cur_row: usize, - cur_col: usize, - max_value: usize, -} + // padding + input.resize(size, ' '); -impl SquareIndexer { - fn new(rows: usize, cols: usize) -> SquareIndexer { - SquareIndexer { - rows, - cols, - cur_row: 0, - cur_col: 0, - max_value: rows * cols, - } - } -} + // prevent input from being moved into closure below + let input = &input; -impl Iterator for SquareIndexer { - type Item = usize; - fn next(&mut self) -> Option { - let value = self.cur_row + (self.cur_col * self.rows); - let output = if value < self.max_value && self.cur_row < self.rows { - Some(value) - } else { - None - }; + // transpose + let mut res: String = (0..width) + .flat_map(|col| { + (0..last_row) + .map(move |row| input[row * width + col]) + .chain(std::iter::once(' ')) + }) + .collect(); - // now increment internal state to next value - self.cur_col += 1; - if self.cur_col >= self.cols { - self.cur_col = 0; - self.cur_row += 1; - } + // trailing space separator + res.pop(); - output - } + res } diff --git a/exercises/practice/crypto-square/.meta/test_template.tera b/exercises/practice/crypto-square/.meta/test_template.tera new file mode 100644 index 000000000..9a2468871 --- /dev/null +++ b/exercises/practice/crypto-square/.meta/test_template.tera @@ -0,0 +1,11 @@ +use crypto_square::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let actual = encrypt({{ test.input.plaintext | json_encode() }}); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(&actual, expected); +} +{% endfor -%} diff --git a/exercises/practice/crypto-square/.meta/tests.toml b/exercises/practice/crypto-square/.meta/tests.toml index be690e975..085d142ea 100644 --- a/exercises/practice/crypto-square/.meta/tests.toml +++ b/exercises/practice/crypto-square/.meta/tests.toml @@ -1,3 +1,34 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# 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" diff --git a/exercises/practice/crypto-square/tests/crypto-square.rs b/exercises/practice/crypto-square/tests/crypto-square.rs index ebe67f3a9..7fac9531f 100644 --- a/exercises/practice/crypto-square/tests/crypto-square.rs +++ b/exercises/practice/crypto-square/tests/crypto-square.rs @@ -1,79 +1,64 @@ -use crypto_square::encrypt; +use crypto_square::*; -fn test(input: &str, output: &str) { - assert_eq!(&encrypt(input), output); +#[test] +fn empty_plaintext_results_in_an_empty_ciphertext() { + let actual = encrypt(""); + let expected = ""; + assert_eq!(&actual, expected); } #[test] -fn empty_input() { - test("", "") +#[ignore] +fn normalization_results_in_empty_plaintext() { + let actual = encrypt("... --- ..."); + let expected = ""; + assert_eq!(&actual, expected); } #[test] #[ignore] -fn encrypt_also_decrypts_square() { - // note that you only get the exact input back if: - // 1. no punctuation - // 2. even spacing - // 3. all lowercase - // 4. square input - let example = "lime anda coco anut"; - assert_eq!(example, &encrypt(&encrypt(example))); +fn lowercase() { + let actual = encrypt("A"); + let expected = "a"; + assert_eq!(&actual, expected); } #[test] #[ignore] -fn example() { - test( - "If man was meant to stay on the ground, god would have given us roots.", - "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau ", - ) +fn remove_spaces() { + let actual = encrypt(" b "); + let expected = "b"; + assert_eq!(&actual, expected); } #[test] #[ignore] -fn empty_last_line() { - test("congratulate", "crl oaa ntt gue") +fn remove_punctuation() { + let actual = encrypt("@1,%!"); + let expected = "1"; + assert_eq!(&actual, expected); } #[test] #[ignore] -fn spaces_are_reorganized() { - test("abet", "ae bt"); - test("a bet", "ae bt"); - test(" a b e t ", "ae bt"); +fn test_9_character_plaintext_results_in_3_chunks_of_3_characters() { + let actual = encrypt("This is fun!"); + let expected = "tsf hiu isn"; + assert_eq!(&actual, expected); } #[test] #[ignore] -fn everything_becomes_lowercase() { - test("caSe", "cs ae"); - test("cAsE", "cs ae"); - test("CASE", "cs ae"); +fn test_8_character_plaintext_results_in_3_chunks_the_last_one_with_a_trailing_space() { + let actual = encrypt("Chill out."); + let expected = "clu hlt io "; + assert_eq!(&actual, expected); } #[test] #[ignore] -fn long() { - test( - r#" -We choose to go to the moon. - -We choose to go to the moon in this decade and do the other things, -not because they are easy, but because they are hard, because that -goal will serve to organize and measure the best of our energies and -skills, because that challenge is one that we are willing to accept, -one we are unwilling to postpone, and one which we intend to win, -and the others, too. - --- John F. Kennedy, 12 September 1962 - "#, - &(String::from("womdbudlmecsgwdwob enooetbsenaotioihe ") - + "cwotcbeeaeunolnnnr henhaecrsrsealeaf1 ocieucavugetciwnk9 " - + "ohnosauerithcnhde6 sotteusteehaegitn2 eohhtseotsatptchn " - + "tsiehetohatwtohee oesrethrenceopwod gtdtyhagbdhanoety " - + "ooehaetaesaresih1 tgcirygnsklewtne2 ooaneaoitilweptrs " - + "ttdgerazoleiaoese hoesaeleflnlrnntp etanshwaosgleedot " - + "mhnoyainubeiuatoe oedtbrldreinnnojm "), - ) +fn test_54_character_plaintext_results_in_7_chunks_the_last_two_with_trailing_spaces() { + let actual = encrypt("If man was meant to stay on the ground, god would have given us roots."); + let expected = "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau "; + assert_eq!(&actual, expected); }