From e2ac07f7091537d79fb864f42b2ab586c7dbd1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Ill=C3=A9s?= Date: Wed, 16 Nov 2016 20:29:22 +0100 Subject: [PATCH 1/8] create alphametics rust project --- exercises/alphametics/Cargo.lock | 4 ++ exercises/alphametics/Cargo.toml | 3 + exercises/alphametics/example.rs | 77 ++++++++++++++++++++++ exercises/alphametics/src/lib.rs | 5 ++ exercises/alphametics/tests/alphametics.rs | 64 ++++++++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 exercises/alphametics/Cargo.lock create mode 100644 exercises/alphametics/Cargo.toml create mode 100644 exercises/alphametics/example.rs create mode 100644 exercises/alphametics/src/lib.rs create mode 100644 exercises/alphametics/tests/alphametics.rs diff --git a/exercises/alphametics/Cargo.lock b/exercises/alphametics/Cargo.lock new file mode 100644 index 000000000..2dea6a4ac --- /dev/null +++ b/exercises/alphametics/Cargo.lock @@ -0,0 +1,4 @@ +[root] +name = "alphametics" +version = "0.0.0" + diff --git a/exercises/alphametics/Cargo.toml b/exercises/alphametics/Cargo.toml new file mode 100644 index 000000000..820638315 --- /dev/null +++ b/exercises/alphametics/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "alphametics" +version = "0.0.0" diff --git a/exercises/alphametics/example.rs b/exercises/alphametics/example.rs new file mode 100644 index 000000000..439410c79 --- /dev/null +++ b/exercises/alphametics/example.rs @@ -0,0 +1,77 @@ +// This is a brute-force solution, use `cargo test --release` for faster testing + +// Use these dependencies in Cargo.toml +// +// [dependencies] +// itertools = "0.5" +// permutohedron = "0.2" + + +extern crate itertools; +extern crate permutohedron; + +use itertools::Itertools; +use permutohedron::Heap as Permutations; + + +use std::collections::HashMap; +use std::collections::HashSet; +use std::char; + +fn test_equation(puzzle: &str, substitutions: &HashMap) -> bool { + // Create a new String with characters changed to numbers + let puzzle: String = puzzle.chars().map(|c| { + if let Some(&n) = substitutions.get(&c) { + // If the character is in the substitutions, get the number and + // convert it to a char + char::from_digit(n as u32,10).unwrap() + } else { + // Otherwise just copy over the character + c + } + }).collect(); + + // Split the puzzle into left and right side + let equation: Vec<&str> = puzzle.split("==").collect(); + + // Parse the number on the right side + let right = equation[1].trim().parse::().unwrap(); + + // Sum the parts on the left side + let left: u32 = equation[0].split('+').map(str::trim).map(|n| n.parse::().unwrap()).sum(); + + // Create a String with just the numbers and spaces + let just_numbers = puzzle.chars().filter(|c| c.is_digit(10) || c.is_whitespace()).collect::(); + // Split this into the numbers and check every number's first character + let no_leading_zeroes = just_numbers.split_whitespace().all(|number| number.chars().next().unwrap() != '0'); + + // Return true if left and right side is equal and the equation doesnt + // contain leading zeroes. + left == right && no_leading_zeroes +} + + +pub fn solve(puzzle: &str) -> Option> { + // Get unique letters from the puzzle + let letters: HashSet = puzzle.chars().filter(|&c| c.is_alphabetic() && c.is_uppercase()).collect(); + let letters: Vec = letters.into_iter().collect(); + + // All available numbers for substitution + let numbers: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + + // Iterate every combination with the length of uniqe letters in the puzzle + for combinations in numbers.iter().combinations(letters.len()) { + let mut c = combinations; + let permutations = Permutations::new(&mut c); + // Iterate every permutation of a letter combination + for p in permutations { + let substitution: HashMap = letters.iter().zip(p).map(|(&c, &n)| (c, n)).collect(); + if test_equation(puzzle, &substitution) { + // We found a good substitution + return Some(substitution); + } + } + } + // If we tested every combination and did not found a solution then return None + None +} \ No newline at end of file diff --git a/exercises/alphametics/src/lib.rs b/exercises/alphametics/src/lib.rs new file mode 100644 index 000000000..f856a7aed --- /dev/null +++ b/exercises/alphametics/src/lib.rs @@ -0,0 +1,5 @@ +use std::collections::HashMap; + +pub fn solve(puzzle: &str) -> Option> { + unimplemented!() +} diff --git a/exercises/alphametics/tests/alphametics.rs b/exercises/alphametics/tests/alphametics.rs new file mode 100644 index 000000000..ddfa31866 --- /dev/null +++ b/exercises/alphametics/tests/alphametics.rs @@ -0,0 +1,64 @@ +extern crate alphametics; +use std::collections::HashMap; + +fn test_helper(puzzle: &str, solution: &[(char, u8)]) { + let answer = alphametics::solve(puzzle).unwrap(); + let solution: HashMap = solution.iter().cloned().collect(); + assert_eq!(answer, solution); +} + +#[test] +fn test_with_three_letters() { + test_helper("I + BB == ILL", + &[('I', 1), ('B', 9), ('L', 0)]); +} + +#[test] +#[ignore] +fn test_must_have_unique_value_for_each_letter() { + let answer = alphametics::solve("A == B"); + assert_eq!(answer, None); +} + +#[test] +#[ignore] +fn test_leading_zero_solution_is_invalid() { + let answer = alphametics::solve("ACA + DD == BD"); + assert_eq!(answer, None); +} + +#[test] +#[ignore] +fn test_puzzle_with_four_letters() { + test_helper("AS + A == MOM", &[('A', 9), ('S', 2), ('M', 1), ('O', 0)]); +} + +#[test] +#[ignore] +fn test_puzzle_with_six_letters() { + test_helper("NO + NO + TOO == LATE", + &[('N', 7), ('O', 4), ('T', 9), ('L', 1), ('A', 0), ('E', 2)]); +} + +#[test] +#[ignore] +fn test_puzzle_with_seven_letters() { + test_helper("HE + SEES + THE == LIGHT", + &[('E', 4), ('G', 2), ('H', 5), ('I', 0), ('L', 1), ('S', 9), ('T', 7)]); +} + +#[test] +#[ignore] +fn test_puzzle_with_eight_letters() { + test_helper("SEND + MORE == MONEY", + &[('S', 9), ('E', 5), ('N', 6), ('D', 7), + ('M', 1), ('O', 0), ('R', 8), ('Y', 2)]); +} + +#[test] +#[ignore] +fn test_puzzle_with_ten_letters() { + test_helper("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE", + &[ ('A', 5), ('D', 3), ('E', 4), ('F', 7), ('G', 8), + ('N', 0), ('O', 2), ('R', 1), ('S', 6), ('T', 9)]); +} From ac7359292b5ec39a82e2eaff2fd081b4c44b206b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Ill=C3=A9s?= Date: Wed, 16 Nov 2016 20:29:39 +0100 Subject: [PATCH 2/8] add alphametics to config.json --- config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index 89a8fd46b..fd05de278 100644 --- a/config.json +++ b/config.json @@ -49,7 +49,8 @@ "rectangles", "forth", "circular-buffer", - "react" + "react", + "alphametics" ], "deprecated": [ "hexadecimal" From 8ae01a5b36189e09d717fa99ed569bf224fe8bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Ill=C3=A9s?= Date: Mon, 21 Nov 2016 18:40:47 +0100 Subject: [PATCH 3/8] Add Cargo-example.toml --- exercises/alphametics/Cargo-example.toml | 7 +++++++ exercises/alphametics/example.rs | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 exercises/alphametics/Cargo-example.toml diff --git a/exercises/alphametics/Cargo-example.toml b/exercises/alphametics/Cargo-example.toml new file mode 100644 index 000000000..37c47d863 --- /dev/null +++ b/exercises/alphametics/Cargo-example.toml @@ -0,0 +1,7 @@ +[package] +name = "alphametics" +version = "0.0.0" + +[dependencies] +itertools = "0.5" +permutohedron = "0.2" \ No newline at end of file diff --git a/exercises/alphametics/example.rs b/exercises/alphametics/example.rs index 439410c79..2dd3bcc0d 100644 --- a/exercises/alphametics/example.rs +++ b/exercises/alphametics/example.rs @@ -1,12 +1,5 @@ // This is a brute-force solution, use `cargo test --release` for faster testing -// Use these dependencies in Cargo.toml -// -// [dependencies] -// itertools = "0.5" -// permutohedron = "0.2" - - extern crate itertools; extern crate permutohedron; From b5eaccee7215f215ecd900ecaa80c432edea95ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Ill=C3=A9s?= Date: Fri, 25 Nov 2016 15:08:01 +0100 Subject: [PATCH 4/8] fix a typo --- exercises/alphametics/example.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/alphametics/example.rs b/exercises/alphametics/example.rs index 2dd3bcc0d..4b13f9cfc 100644 --- a/exercises/alphametics/example.rs +++ b/exercises/alphametics/example.rs @@ -52,7 +52,7 @@ pub fn solve(puzzle: &str) -> Option> { // All available numbers for substitution let numbers: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - // Iterate every combination with the length of uniqe letters in the puzzle + // Iterate every combination with the length of unique letters in the puzzle for combinations in numbers.iter().combinations(letters.len()) { let mut c = combinations; let permutations = Permutations::new(&mut c); From 852302938ac9fb5f23a512678a6666abd7f48205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Ill=C3=A9s?= Date: Fri, 25 Nov 2016 15:31:57 +0100 Subject: [PATCH 5/8] Place the exercise in "Getting Rusty" --- config.json | 4 ++-- problems.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index fd05de278..05d3a298a 100644 --- a/config.json +++ b/config.json @@ -39,6 +39,7 @@ "wordy", "tournament", "custom-set", + "alphametics", "anagram", "nucleotide-codons", "robot-name", @@ -49,8 +50,7 @@ "rectangles", "forth", "circular-buffer", - "react", - "alphametics" + "react" ], "deprecated": [ "hexadecimal" diff --git a/problems.md b/problems.md index 41df24594..db10965be 100644 --- a/problems.md +++ b/problems.md @@ -58,6 +58,7 @@ phone-number | option, format, unwrap_or, iters, match wordy | Result, string parsing, operators (optional) tournament | enum, sorting, hashmap, structs custom-set | generic over type, vector, equality, struct +alphametics | string parsing, combinations, math, external crates (optional) ## Rust Gets Strange From ff83051dfbe953231d948bf6c7f60931b2bfcb8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Ill=C3=A9s?= Date: Mon, 28 Nov 2016 18:11:51 +0100 Subject: [PATCH 6/8] add newline at the end of file --- exercises/alphametics/Cargo-example.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/alphametics/Cargo-example.toml b/exercises/alphametics/Cargo-example.toml index 37c47d863..ef339dd65 100644 --- a/exercises/alphametics/Cargo-example.toml +++ b/exercises/alphametics/Cargo-example.toml @@ -4,4 +4,4 @@ version = "0.0.0" [dependencies] itertools = "0.5" -permutohedron = "0.2" \ No newline at end of file +permutohedron = "0.2" From 44171e5265b222ad4ebd92ee8a3f34f188d9ba51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Ill=C3=A9s?= Date: Mon, 28 Nov 2016 18:12:03 +0100 Subject: [PATCH 7/8] run rustfmt on rust source code --- exercises/alphametics/example.rs | 36 +++++++++++++--------- exercises/alphametics/tests/alphametics.rs | 16 +++++----- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/exercises/alphametics/example.rs b/exercises/alphametics/example.rs index 4b13f9cfc..c5f3a3ce2 100644 --- a/exercises/alphametics/example.rs +++ b/exercises/alphametics/example.rs @@ -13,16 +13,18 @@ use std::char; fn test_equation(puzzle: &str, substitutions: &HashMap) -> bool { // Create a new String with characters changed to numbers - let puzzle: String = puzzle.chars().map(|c| { - if let Some(&n) = substitutions.get(&c) { - // If the character is in the substitutions, get the number and - // convert it to a char - char::from_digit(n as u32,10).unwrap() - } else { - // Otherwise just copy over the character - c - } - }).collect(); + let puzzle: String = puzzle.chars() + .map(|c| { + if let Some(&n) = substitutions.get(&c) { + // If the character is in the substitutions, get the number and + // convert it to a char + char::from_digit(n as u32, 10).unwrap() + } else { + // Otherwise just copy over the character + c + } + }) + .collect(); // Split the puzzle into left and right side let equation: Vec<&str> = puzzle.split("==").collect(); @@ -34,9 +36,11 @@ fn test_equation(puzzle: &str, substitutions: &HashMap) -> bool { let left: u32 = equation[0].split('+').map(str::trim).map(|n| n.parse::().unwrap()).sum(); // Create a String with just the numbers and spaces - let just_numbers = puzzle.chars().filter(|c| c.is_digit(10) || c.is_whitespace()).collect::(); + let just_numbers = + puzzle.chars().filter(|c| c.is_digit(10) || c.is_whitespace()).collect::(); // Split this into the numbers and check every number's first character - let no_leading_zeroes = just_numbers.split_whitespace().all(|number| number.chars().next().unwrap() != '0'); + let no_leading_zeroes = just_numbers.split_whitespace() + .all(|number| number.chars().next().unwrap() != '0'); // Return true if left and right side is equal and the equation doesnt // contain leading zeroes. @@ -46,7 +50,8 @@ fn test_equation(puzzle: &str, substitutions: &HashMap) -> bool { pub fn solve(puzzle: &str) -> Option> { // Get unique letters from the puzzle - let letters: HashSet = puzzle.chars().filter(|&c| c.is_alphabetic() && c.is_uppercase()).collect(); + let letters: HashSet = + puzzle.chars().filter(|&c| c.is_alphabetic() && c.is_uppercase()).collect(); let letters: Vec = letters.into_iter().collect(); // All available numbers for substitution @@ -58,7 +63,8 @@ pub fn solve(puzzle: &str) -> Option> { let permutations = Permutations::new(&mut c); // Iterate every permutation of a letter combination for p in permutations { - let substitution: HashMap = letters.iter().zip(p).map(|(&c, &n)| (c, n)).collect(); + let substitution: HashMap = + letters.iter().zip(p).map(|(&c, &n)| (c, n)).collect(); if test_equation(puzzle, &substitution) { // We found a good substitution return Some(substitution); @@ -67,4 +73,4 @@ pub fn solve(puzzle: &str) -> Option> { } // If we tested every combination and did not found a solution then return None None -} \ No newline at end of file +} diff --git a/exercises/alphametics/tests/alphametics.rs b/exercises/alphametics/tests/alphametics.rs index ddfa31866..bedec5c09 100644 --- a/exercises/alphametics/tests/alphametics.rs +++ b/exercises/alphametics/tests/alphametics.rs @@ -1,7 +1,7 @@ extern crate alphametics; use std::collections::HashMap; -fn test_helper(puzzle: &str, solution: &[(char, u8)]) { +fn test_helper(puzzle: &str, solution: &[(char, u8)]) { let answer = alphametics::solve(puzzle).unwrap(); let solution: HashMap = solution.iter().cloned().collect(); assert_eq!(answer, solution); @@ -9,8 +9,7 @@ fn test_helper(puzzle: &str, solution: &[(char, u8)]) { #[test] fn test_with_three_letters() { - test_helper("I + BB == ILL", - &[('I', 1), ('B', 9), ('L', 0)]); + test_helper("I + BB == ILL", &[('I', 1), ('B', 9), ('L', 0)]); } #[test] @@ -37,28 +36,27 @@ fn test_puzzle_with_four_letters() { #[ignore] fn test_puzzle_with_six_letters() { test_helper("NO + NO + TOO == LATE", - &[('N', 7), ('O', 4), ('T', 9), ('L', 1), ('A', 0), ('E', 2)]); + &[('N', 7), ('O', 4), ('T', 9), ('L', 1), ('A', 0), ('E', 2)]); } #[test] #[ignore] fn test_puzzle_with_seven_letters() { test_helper("HE + SEES + THE == LIGHT", - &[('E', 4), ('G', 2), ('H', 5), ('I', 0), ('L', 1), ('S', 9), ('T', 7)]); + &[('E', 4), ('G', 2), ('H', 5), ('I', 0), ('L', 1), ('S', 9), ('T', 7)]); } #[test] #[ignore] fn test_puzzle_with_eight_letters() { test_helper("SEND + MORE == MONEY", - &[('S', 9), ('E', 5), ('N', 6), ('D', 7), - ('M', 1), ('O', 0), ('R', 8), ('Y', 2)]); + &[('S', 9), ('E', 5), ('N', 6), ('D', 7), ('M', 1), ('O', 0), ('R', 8), ('Y', 2)]); } #[test] #[ignore] fn test_puzzle_with_ten_letters() { test_helper("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE", - &[ ('A', 5), ('D', 3), ('E', 4), ('F', 7), ('G', 8), - ('N', 0), ('O', 2), ('R', 1), ('S', 6), ('T', 9)]); + &[('A', 5), ('D', 3), ('E', 4), ('F', 7), ('G', 8), ('N', 0), ('O', 2), ('R', 1), + ('S', 6), ('T', 9)]); } From d3a54359e152b871a690ba69059446b3feb4cbb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Ill=C3=A9s?= Date: Mon, 28 Nov 2016 18:14:14 +0100 Subject: [PATCH 8/8] Rename test_helper function to something more meaningful --- exercises/alphametics/tests/alphametics.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/exercises/alphametics/tests/alphametics.rs b/exercises/alphametics/tests/alphametics.rs index bedec5c09..e4c796060 100644 --- a/exercises/alphametics/tests/alphametics.rs +++ b/exercises/alphametics/tests/alphametics.rs @@ -1,7 +1,7 @@ extern crate alphametics; use std::collections::HashMap; -fn test_helper(puzzle: &str, solution: &[(char, u8)]) { +fn assert_alphametic_solution_eq(puzzle: &str, solution: &[(char, u8)]) { let answer = alphametics::solve(puzzle).unwrap(); let solution: HashMap = solution.iter().cloned().collect(); assert_eq!(answer, solution); @@ -9,7 +9,7 @@ fn test_helper(puzzle: &str, solution: &[(char, u8)]) { #[test] fn test_with_three_letters() { - test_helper("I + BB == ILL", &[('I', 1), ('B', 9), ('L', 0)]); + assert_alphametic_solution_eq("I + BB == ILL", &[('I', 1), ('B', 9), ('L', 0)]); } #[test] @@ -29,34 +29,34 @@ fn test_leading_zero_solution_is_invalid() { #[test] #[ignore] fn test_puzzle_with_four_letters() { - test_helper("AS + A == MOM", &[('A', 9), ('S', 2), ('M', 1), ('O', 0)]); + assert_alphametic_solution_eq("AS + A == MOM", &[('A', 9), ('S', 2), ('M', 1), ('O', 0)]); } #[test] #[ignore] fn test_puzzle_with_six_letters() { - test_helper("NO + NO + TOO == LATE", + assert_alphametic_solution_eq("NO + NO + TOO == LATE", &[('N', 7), ('O', 4), ('T', 9), ('L', 1), ('A', 0), ('E', 2)]); } #[test] #[ignore] fn test_puzzle_with_seven_letters() { - test_helper("HE + SEES + THE == LIGHT", + assert_alphametic_solution_eq("HE + SEES + THE == LIGHT", &[('E', 4), ('G', 2), ('H', 5), ('I', 0), ('L', 1), ('S', 9), ('T', 7)]); } #[test] #[ignore] fn test_puzzle_with_eight_letters() { - test_helper("SEND + MORE == MONEY", + assert_alphametic_solution_eq("SEND + MORE == MONEY", &[('S', 9), ('E', 5), ('N', 6), ('D', 7), ('M', 1), ('O', 0), ('R', 8), ('Y', 2)]); } #[test] #[ignore] fn test_puzzle_with_ten_letters() { - test_helper("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE", + assert_alphametic_solution_eq("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE", &[('A', 5), ('D', 3), ('E', 4), ('F', 7), ('G', 8), ('N', 0), ('O', 2), ('R', 1), ('S', 6), ('T', 9)]); }