diff --git a/config.json b/config.json index 89a8fd46b..05d3a298a 100644 --- a/config.json +++ b/config.json @@ -39,6 +39,7 @@ "wordy", "tournament", "custom-set", + "alphametics", "anagram", "nucleotide-codons", "robot-name", diff --git a/exercises/alphametics/Cargo-example.toml b/exercises/alphametics/Cargo-example.toml new file mode 100644 index 000000000..ef339dd65 --- /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" 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..c5f3a3ce2 --- /dev/null +++ b/exercises/alphametics/example.rs @@ -0,0 +1,76 @@ +// This is a brute-force solution, use `cargo test --release` for faster testing + +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 unique 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 +} 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..e4c796060 --- /dev/null +++ b/exercises/alphametics/tests/alphametics.rs @@ -0,0 +1,62 @@ +extern crate alphametics; +use std::collections::HashMap; + +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); +} + +#[test] +fn test_with_three_letters() { + assert_alphametic_solution_eq("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() { + assert_alphametic_solution_eq("AS + A == MOM", &[('A', 9), ('S', 2), ('M', 1), ('O', 0)]); +} + +#[test] +#[ignore] +fn test_puzzle_with_six_letters() { + 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() { + 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() { + 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() { + 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)]); +} 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