Skip to content

Commit 531434e

Browse files
ijanospetertseng
authored andcommitted
Add Alphametics exercise. Fixes #182 (#225)
1 parent a3cae07 commit 531434e

File tree

8 files changed

+159
-0
lines changed

8 files changed

+159
-0
lines changed

config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"wordy",
4040
"tournament",
4141
"custom-set",
42+
"alphametics",
4243
"anagram",
4344
"nucleotide-codons",
4445
"robot-name",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "alphametics"
3+
version = "0.0.0"
4+
5+
[dependencies]
6+
itertools = "0.5"
7+
permutohedron = "0.2"

exercises/alphametics/Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

exercises/alphametics/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[package]
2+
name = "alphametics"
3+
version = "0.0.0"

exercises/alphametics/example.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// This is a brute-force solution, use `cargo test --release` for faster testing
2+
3+
extern crate itertools;
4+
extern crate permutohedron;
5+
6+
use itertools::Itertools;
7+
use permutohedron::Heap as Permutations;
8+
9+
10+
use std::collections::HashMap;
11+
use std::collections::HashSet;
12+
use std::char;
13+
14+
fn test_equation(puzzle: &str, substitutions: &HashMap<char, u8>) -> bool {
15+
// Create a new String with characters changed to numbers
16+
let puzzle: String = puzzle.chars()
17+
.map(|c| {
18+
if let Some(&n) = substitutions.get(&c) {
19+
// If the character is in the substitutions, get the number and
20+
// convert it to a char
21+
char::from_digit(n as u32, 10).unwrap()
22+
} else {
23+
// Otherwise just copy over the character
24+
c
25+
}
26+
})
27+
.collect();
28+
29+
// Split the puzzle into left and right side
30+
let equation: Vec<&str> = puzzle.split("==").collect();
31+
32+
// Parse the number on the right side
33+
let right = equation[1].trim().parse::<u32>().unwrap();
34+
35+
// Sum the parts on the left side
36+
let left: u32 = equation[0].split('+').map(str::trim).map(|n| n.parse::<u32>().unwrap()).sum();
37+
38+
// Create a String with just the numbers and spaces
39+
let just_numbers =
40+
puzzle.chars().filter(|c| c.is_digit(10) || c.is_whitespace()).collect::<String>();
41+
// Split this into the numbers and check every number's first character
42+
let no_leading_zeroes = just_numbers.split_whitespace()
43+
.all(|number| number.chars().next().unwrap() != '0');
44+
45+
// Return true if left and right side is equal and the equation doesnt
46+
// contain leading zeroes.
47+
left == right && no_leading_zeroes
48+
}
49+
50+
51+
pub fn solve(puzzle: &str) -> Option<HashMap<char, u8>> {
52+
// Get unique letters from the puzzle
53+
let letters: HashSet<char> =
54+
puzzle.chars().filter(|&c| c.is_alphabetic() && c.is_uppercase()).collect();
55+
let letters: Vec<char> = letters.into_iter().collect();
56+
57+
// All available numbers for substitution
58+
let numbers: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
59+
60+
// Iterate every combination with the length of unique letters in the puzzle
61+
for combinations in numbers.iter().combinations(letters.len()) {
62+
let mut c = combinations;
63+
let permutations = Permutations::new(&mut c);
64+
// Iterate every permutation of a letter combination
65+
for p in permutations {
66+
let substitution: HashMap<char, u8> =
67+
letters.iter().zip(p).map(|(&c, &n)| (c, n)).collect();
68+
if test_equation(puzzle, &substitution) {
69+
// We found a good substitution
70+
return Some(substitution);
71+
}
72+
}
73+
}
74+
// If we tested every combination and did not found a solution then return None
75+
None
76+
}

exercises/alphametics/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
use std::collections::HashMap;
2+
3+
pub fn solve(puzzle: &str) -> Option<HashMap<char, u8>> {
4+
unimplemented!()
5+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
extern crate alphametics;
2+
use std::collections::HashMap;
3+
4+
fn assert_alphametic_solution_eq(puzzle: &str, solution: &[(char, u8)]) {
5+
let answer = alphametics::solve(puzzle).unwrap();
6+
let solution: HashMap<char, u8> = solution.iter().cloned().collect();
7+
assert_eq!(answer, solution);
8+
}
9+
10+
#[test]
11+
fn test_with_three_letters() {
12+
assert_alphametic_solution_eq("I + BB == ILL", &[('I', 1), ('B', 9), ('L', 0)]);
13+
}
14+
15+
#[test]
16+
#[ignore]
17+
fn test_must_have_unique_value_for_each_letter() {
18+
let answer = alphametics::solve("A == B");
19+
assert_eq!(answer, None);
20+
}
21+
22+
#[test]
23+
#[ignore]
24+
fn test_leading_zero_solution_is_invalid() {
25+
let answer = alphametics::solve("ACA + DD == BD");
26+
assert_eq!(answer, None);
27+
}
28+
29+
#[test]
30+
#[ignore]
31+
fn test_puzzle_with_four_letters() {
32+
assert_alphametic_solution_eq("AS + A == MOM", &[('A', 9), ('S', 2), ('M', 1), ('O', 0)]);
33+
}
34+
35+
#[test]
36+
#[ignore]
37+
fn test_puzzle_with_six_letters() {
38+
assert_alphametic_solution_eq("NO + NO + TOO == LATE",
39+
&[('N', 7), ('O', 4), ('T', 9), ('L', 1), ('A', 0), ('E', 2)]);
40+
}
41+
42+
#[test]
43+
#[ignore]
44+
fn test_puzzle_with_seven_letters() {
45+
assert_alphametic_solution_eq("HE + SEES + THE == LIGHT",
46+
&[('E', 4), ('G', 2), ('H', 5), ('I', 0), ('L', 1), ('S', 9), ('T', 7)]);
47+
}
48+
49+
#[test]
50+
#[ignore]
51+
fn test_puzzle_with_eight_letters() {
52+
assert_alphametic_solution_eq("SEND + MORE == MONEY",
53+
&[('S', 9), ('E', 5), ('N', 6), ('D', 7), ('M', 1), ('O', 0), ('R', 8), ('Y', 2)]);
54+
}
55+
56+
#[test]
57+
#[ignore]
58+
fn test_puzzle_with_ten_letters() {
59+
assert_alphametic_solution_eq("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE",
60+
&[('A', 5), ('D', 3), ('E', 4), ('F', 7), ('G', 8), ('N', 0), ('O', 2), ('R', 1),
61+
('S', 6), ('T', 9)]);
62+
}

problems.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ phone-number | option, format, unwrap_or, iters, match
5858
wordy | Result, string parsing, operators (optional)
5959
tournament | enum, sorting, hashmap, structs
6060
custom-set | generic over type, vector, equality, struct
61+
alphametics | string parsing, combinations, math, external crates (optional)
6162

6263
## Rust Gets Strange
6364

0 commit comments

Comments
 (0)