Skip to content

Instantly share code, notes, and snippets.

@Eraden
Last active August 4, 2021 13:31
Show Gist options
  • Save Eraden/92134f6cc69941c6fa7d8f251ebc4110 to your computer and use it in GitHub Desktop.
Save Eraden/92134f6cc69941c6fa7d8f251ebc4110 to your computer and use it in GitHub Desktop.
Way faster version
#![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();
}
});
}
}
#![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();
}
});
}
}
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