use std::cmp::max;
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::ops::RangeInclusive;
use std::path::Path;
use crate::random::randomized;
use crate::{Error, STORAGE_KEY_LENGTH, read_lines};
#[derive(Copy, Clone)]
pub enum PopulationSize {
Bhutan = 727_145,
Belgium = 11_742_796,
Brazil = 203_080_756,
}
pub fn ingredients<P1, P2>(
static_name: &str,
size: PopulationSize,
prefixes: P1,
colors: P1,
animals: P1,
output: P2,
) -> Result<(), Error>
where
P1: AsRef<Path>,
P2: AsRef<Path>,
{
let prefixes_path: &Path = prefixes.as_ref();
let colors_path: &Path = colors.as_ref();
let animals_path: &Path = animals.as_ref();
let output_path: &Path = output.as_ref();
let required_prefixes = 16u32.pow(STORAGE_KEY_LENGTH as u32);
let prefix_count = count_lines(prefixes_path)?;
if prefix_count < required_prefixes {
return Err(Error::Codegen(format!(
"insufficient seed words. {}. {}",
format_args!("{prefixes_path:#?} ({prefix_count} words)"),
format_args!(
"{} words available, but {} needed",
prefix_count, required_prefixes
)
)));
}
let required_color_animals = size as u32 / required_prefixes;
let color_count = count_lines(colors_path)?;
let animal_count = count_lines(animals_path)?;
if required_color_animals > color_count * animal_count {
return Err(Error::Codegen(format!(
"insufficient seed words. {}. {}",
format_args!(
"{colors_path:#?} ({} words), {animals_path:#?} ({} words)",
color_count, animal_count
),
format_args!(
"{} combinations available, but {} needed",
color_count * animal_count,
required_color_animals
)
)));
}
let mut output_writer = BufWriter::new(File::create(output_path).unwrap());
writeln!(output_writer, "#[allow(dead_code)]")?;
writeln!(output_writer, "pub static {}:", static_name.to_uppercase())?;
writeln!(
output_writer,
"(usize, phf::Map<&str, &str>, &[&str], &[&str]) = ("
)?;
writeln!(output_writer, "{},", size as usize)?;
write_prefixes(prefixes_path, &mut output_writer)?;
write_words(colors_path, &mut output_writer)?;
write_words(animals_path, &mut output_writer)?;
writeln!(output_writer, ");")?;
Ok(())
}
fn write_prefixes(input: &Path, output: &mut BufWriter<File>) -> Result<(), Error> {
let hex_digits = "0123456789abcdef".chars().collect::<Vec<_>>();
let mut hex_keys = vec![];
find_combinations(
STORAGE_KEY_LENGTH..=STORAGE_KEY_LENGTH,
hex_digits.as_slice(),
&mut hex_keys,
);
let rng_seed = 656437432927126634;
let prefix_words = read_lines(input)?
.map_while(Result::ok)
.take(hex_keys.len())
.collect::<Vec<String>>();
let prefix_words = prefix_words.iter().map(|w| &w[..]).collect::<Vec<&str>>();
let prefix_words = randomized(prefix_words.as_slice(), rng_seed);
assert_eq!(hex_keys.len(), prefix_words.len());
let mut map = &mut phf_codegen::Map::<&'static str>::new();
for (k, v) in hex_keys.iter().zip(prefix_words.iter()) {
map = map.entry(k, format!("\"{v}\""));
}
writeln!(output, "{},", map.build())?;
Ok(())
}
fn write_words(input: &Path, output: &mut BufWriter<File>) -> Result<(), Error> {
let input = read_lines(input)?.map_while(Result::ok);
writeln!(output, "&[")?;
for word in input {
writeln!(output, " \"{word}\",")?;
}
writeln!(output, "],")?;
Ok(())
}
fn find_combinations(lengths: RangeInclusive<usize>, chars: &[char], results: &mut Vec<String>) {
match (lengths.start(), lengths.end()) {
(1, 1) => {
results.append(&mut chars.iter().map(|c| c.to_string()).collect::<Vec<_>>());
}
(&start, &end) => {
let mut seed_results = vec![];
find_combinations(
max(1, start - 1)..=max(1, end - 1),
chars,
&mut seed_results,
);
let mut next_results: Vec<String> = if start == 1 {
seed_results
.iter()
.filter_map(|s| if s.len() < 2 { Some(s.clone()) } else { None })
.collect()
} else {
vec![]
};
for comb in seed_results.iter() {
for c in chars {
let mut next = comb.clone();
next.push(*c);
next_results.push(next);
}
}
results.append(&mut next_results);
}
}
}
fn count_lines(file: &Path) -> Result<u32, std::io::Error> {
let file = File::open(file)?;
let read = BufReader::new(file);
let mut count = 0;
for _ in read.lines().map_while(Result::ok) {
count += 1;
}
Ok(count)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_combinations_base() {
let mut result = vec![];
find_combinations(1..=1, &['a', 'b', 'c'], &mut result);
assert_eq!(result, vec!["a", "b", "c"]);
}
#[test]
fn test_find_combinations_inductive() {
let mut result = vec![];
find_combinations(1..=2, &['a', 'b', 'c'], &mut result);
assert_eq!(
result,
vec![
"a", "b", "c", "aa", "ab", "ac", "ba", "bb", "bc", "ca", "cb", "cc"
]
);
}
}