Last active
August 4, 2021 13:31
-
-
Save Eraden/92134f6cc69941c6fa7d8f251ebc4110 to your computer and use it in GitHub Desktop.
Way faster version
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![feature(test)] | |
extern crate test; | |
use std::collections::HashMap; | |
use std::env::args; | |
use std::fs::File; | |
use std::io::{self, BufRead}; | |
use num_bigint::BigUint; | |
type Dictionary = HashMap<BigUint, Vec<String>>; | |
static DIGITS: [&str; 10] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; | |
/// Port of Peter Norvig's Lisp solution to the Prechelt phone-encoding problem. | |
/// | |
/// Even though this is intended as a port, it deviates quite a bit from it | |
/// due to the very different natures of Lisp and Rust. | |
fn main() -> io::Result<()> { | |
// drop itself from args | |
let mut args = args().skip(1); | |
let words_file = args | |
.next() | |
// skip benches | |
.filter(|s| !s.starts_with("--")) | |
.unwrap_or_else(|| "tests/words.txt".into()); | |
let input_file = args | |
.next() | |
// skip benches | |
.filter(|s| !s.starts_with("--")) | |
.unwrap_or_else(|| "tests/numbers.txt".into()); | |
let dict = load_dict(words_file)?; | |
let mut words = Vec::new(); | |
for num in read_lines(input_file)?.flatten() { | |
let digits: Vec<_> = num.chars().filter(|ch| ch.is_digit(10)).collect(); | |
print_translations(&num, &digits, 0, &mut words, &dict)?; | |
} | |
Ok(()) | |
} | |
fn print_translations<'dict>( | |
num: &str, | |
digits: &[char], | |
start: usize, | |
words: &mut Vec<&'dict str>, | |
dict: &'dict Dictionary, | |
) -> io::Result<()> { | |
if start >= digits.len() { | |
print_solution(num, words); | |
return Ok(()); | |
} | |
let mut n = 1u8.into(); | |
let mut found_word = false; | |
for i in start..digits.len() { | |
n *= 10u8; | |
n += nth_digit(digits, i); | |
if let Some(found_words) = dict.get(&n) { | |
for word in found_words { | |
found_word = true; | |
words.push(word); | |
print_translations(num, digits, i + 1, words, dict)?; | |
words.pop(); | |
} | |
} | |
} | |
if !found_word && !words.last().map(|w| is_digit(w)).unwrap_or(false) { | |
let digit = nth_digit(digits, start); | |
words.push(DIGITS[usize::from(digit)]); | |
print_translations(num, digits, start + 1, words, dict)?; | |
words.pop(); | |
} | |
Ok(()) | |
} | |
fn print_solution(_num: &str, _words: &[&str]) { | |
return; | |
// print!("{}:", num); | |
// for word in words { | |
// print!(" {}", word); | |
// } | |
// println!(); | |
} | |
fn load_dict(words_file: String) -> io::Result<Dictionary> { | |
let mut dict = HashMap::with_capacity(100); | |
for word in read_lines(words_file)?.flatten() { | |
let key = word_to_number(&word); | |
let words = dict.entry(key).or_insert_with(Vec::new); | |
words.push(word); | |
} | |
Ok(dict) | |
} | |
// The output is wrapped in a Result to allow matching on errors | |
// Returns an Iterator to the Reader of the lines of the file. | |
fn read_lines(filename: String) -> io::Result<io::Lines<io::BufReader<File>>> { | |
let file = File::open(&filename).map_err(|e| { | |
eprintln!("{:?} {:?}", e, filename); | |
e | |
})?; | |
Ok(io::BufReader::new(file).lines()) | |
} | |
fn word_to_number(word: &str) -> BigUint { | |
let mut n = 1u8.into(); | |
for digit in word.chars().filter_map(char_to_digit) { | |
n *= 10u8; | |
n += digit; | |
} | |
n | |
} | |
fn nth_digit(digits: &[char], i: usize) -> u8 { | |
digits[i] as u8 - b'0' | |
} | |
fn is_digit(string: &str) -> bool { | |
string.len() == 1 && string.chars().next().unwrap().is_digit(10) | |
} | |
fn char_to_digit(ch: char) -> Option<u8> { | |
Some(match ch.to_ascii_lowercase() { | |
'e' => 0, | |
'j' | 'n' | 'q' => 1, | |
'r' | 'w' | 'x' => 2, | |
'd' | 's' | 'y' => 3, | |
'f' | 't' => 4, | |
'a' | 'm' => 5, | |
'c' | 'i' | 'v' => 6, | |
'b' | 'k' | 'u' => 7, | |
'l' | 'o' | 'p' => 8, | |
'g' | 'h' | 'z' => 9, | |
_ => return None, | |
}) | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
use test::Bencher; | |
#[bench] | |
fn old(b: &mut Bencher) { | |
b.iter(|| { | |
for _ in 0..100 { | |
main().unwrap(); | |
} | |
}); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![feature(test)] | |
extern crate test; | |
use std::collections::HashMap; | |
use std::env::args; | |
use std::io; | |
type Dictionary<'l> = HashMap<u16, Vec<&'l str>>; | |
static DIGITS: [&str; 10] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; | |
fn main() -> io::Result<()> { | |
let mut args = args().skip(1); | |
let words_file = args | |
.next() | |
.filter(|s| !s.starts_with("--")) | |
.unwrap_or_else(|| String::from("tests/words.txt")); | |
let input_file = args | |
.next() | |
.filter(|s| !s.starts_with("--")) | |
.unwrap_or_else(|| String::from("tests/numbers.txt")); | |
// read file | |
let contents = std::fs::read_to_string(words_file)?; | |
let dict = contents.lines().fold( | |
// allocating as many memory as there is lines to prevent reallocation and rehashing | |
HashMap::with_capacity(contents.lines().count()), | |
|mut dict, word| { | |
dict.entry(word_to_number(&word)) | |
.or_insert_with(|| Vec::with_capacity(10)) | |
// create dict without allocating any additional memory | |
.push(word); | |
dict | |
}, | |
); | |
// preallocate words stack | |
let mut words = Vec::with_capacity(100); | |
let contents = std::fs::read_to_string(input_file)?; | |
for num in contents.lines() { | |
// skip allocating and use iterator | |
let digits = num.chars().filter(|ch| ch.is_digit(10)).into_iter(); | |
print_translations(&num, &mut digits.peekable(), &mut words, &dict)?; | |
} | |
Ok(()) | |
} | |
#[inline(always)] | |
fn print_translations<'dict, It: Iterator<Item = char> + Clone>( | |
_num: &str, | |
digits: &mut std::iter::Peekable<It>, | |
words: &mut Vec<&'dict str>, | |
dict: &'dict Dictionary, | |
) -> io::Result<()> { | |
// check if it's end of iterator | |
let first = match digits.peek() { | |
None => { | |
// here would be print but IO will wreck bench | |
return Ok(()); | |
}, | |
// takes first for later use | |
Some(c) => *c, | |
}; | |
let mut found_word = false; | |
let mut n = 1u16; | |
let mut it = digits.clone(); | |
while let Some(digit) = it.next() { | |
n = (n * 10) + (digit as u8 - b'0') as u16; | |
if let Some(found_words) = dict.get(&n) { | |
found_word = true; | |
for word in found_words { | |
words.push(word); | |
print_translations(_num, &mut it, words, &dict)?; | |
words.pop(); | |
} | |
} | |
} | |
digits.next(); | |
if !found_word && !words.last().as_deref().map(is_digit).unwrap_or_default() { | |
let digit = first as u8 - b'0'; | |
words.push(DIGITS[digit as usize]); | |
print_translations(_num, digits, words, &dict)?; | |
words.pop(); | |
} | |
Ok(()) | |
} | |
#[inline(always)] | |
fn word_to_number(word: &str) -> u16 { | |
word.chars() | |
.filter_map(char_to_digit) | |
.fold(1u16, |memo, digit| (memo * 10) + digit as u16) | |
} | |
#[inline(always)] | |
fn is_digit(string: &&str) -> bool { | |
string.len() == 1 | |
&& string | |
.chars() | |
.last() | |
.map(|c| c.is_digit(10)) | |
.unwrap_or_default() | |
} | |
#[inline(always)] | |
fn char_to_digit(ch: char) -> Option<u8> { | |
Some(match ch.to_ascii_lowercase() { | |
'e' => 0, | |
'j' | 'n' | 'q' => 1, | |
'r' | 'w' | 'x' => 2, | |
'd' | 's' | 'y' => 3, | |
'f' | 't' => 4, | |
'a' | 'm' => 5, | |
'c' | 'i' | 'v' => 6, | |
'b' | 'k' | 'u' => 7, | |
'l' | 'o' | 'p' => 8, | |
'g' | 'h' | 'z' => 9, | |
_ => return None, | |
}) | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
use test::Bencher; | |
#[bench] | |
fn opt(b: &mut Bencher) { | |
b.iter(|| { | |
for _ in 0..100 { | |
main().unwrap(); | |
} | |
}); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Finished bench [optimized] target(s) in 0.00s | |
Running unittests (target/release/deps/old-00f65a67e3538a91) | |
running 1 test | |
test tests::old ... bench: 1,197,242 ns/iter (+/- 88,100) | |
test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out; finished in 0.36s | |
Running unittests (target/release/deps/opt-857ae2f91cdd6050) | |
running 1 test | |
test tests::opt ... bench: 657,246 ns/iter (+/- 85,148) | |
test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out; finished in 0.60s |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment