doesn't work
This commit is contained in:
parent
211368ee45
commit
d3f89edddf
1 changed files with 53 additions and 76 deletions
129
src/main.rs
129
src/main.rs
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
array,
|
||||||
collections::{HashMap, hash_map::Entry},
|
collections::HashSet,
|
||||||
env,
|
env,
|
||||||
io::{self, BufRead, BufReader},
|
io::{self, BufRead, BufReader},
|
||||||
process,
|
process,
|
||||||
|
|
@ -22,85 +22,62 @@ fn main() {
|
||||||
|
|
||||||
let target = env::args().nth(1).map_or(LetterSet::FULL, LetterSet::from);
|
let target = env::args().nth(1).map_or(LetterSet::FULL, LetterSet::from);
|
||||||
println!("letter count: {}", target.len());
|
println!("letter count: {}", target.len());
|
||||||
let Some((len, path)) = solve(&mut dictionary, target) else {
|
let (len, path) = solve(&mut dictionary, target);
|
||||||
println!("failed :(");
|
println!("{len}: {}", path.into_iter().collect::<Vec<_>>().join(" "));
|
||||||
process::exit(1);
|
|
||||||
};
|
|
||||||
println!("{len}: {}", path.join(" "));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn solve(dictionary: &mut [(String, LetterSet)], target: LetterSet) -> Option<(u16, Vec<String>)> {
|
const MAX_SOLUTION_LENGTH: u8 = 50;
|
||||||
let mut bests = HashMap::new();
|
|
||||||
bests.insert(LetterSet::EMPTY, (0, Vec::new()));
|
|
||||||
|
|
||||||
// WORDS WITH DUPES MUST BE AT THE END!
|
fn solve(dictionary: &mut [(String, LetterSet)], target: LetterSet) -> (u8, HashSet<String>) {
|
||||||
dictionary.sort_by_key(|(word, ls)| {
|
let mut bests = (0..=LetterSet::FULL.to_index())
|
||||||
let dupes = word.len() - usize::from(ls.len());
|
.map(|_| (u8::MAX, HashSet::new()))
|
||||||
(dupes, cmp::Reverse(ls.len()))
|
.collect::<Box<[_]>>();
|
||||||
});
|
println!("allocated!");
|
||||||
|
bests[LetterSet::FULL.to_index()] = (0, HashSet::new());
|
||||||
|
|
||||||
for (word, ls) in dictionary.iter() {
|
score(dictionary, &mut bests, LetterSet::EMPTY, 0);
|
||||||
let len = u16::try_from(word.len()).unwrap();
|
|
||||||
match bests.entry(*ls) {
|
bests[target.negation().to_index()].clone()
|
||||||
Entry::Occupied(mut occupied) => {
|
}
|
||||||
if occupied.get().0 > len {
|
|
||||||
occupied.insert((len, vec![word.clone()]));
|
// TODO: perhaps bottom up? (pick or no pick) (real potential!) (not obvious how to do path)
|
||||||
}
|
// the problem is that i'm not *improving* scores, i use the best one immediately
|
||||||
}
|
// length doesn't include spaces. i think that's okay
|
||||||
Entry::Vacant(vacant) => {
|
// bests is sorta inverted; the keys are completed letters
|
||||||
vacant.insert((len, vec![word.clone()]));
|
// assumes that `bests[LetterSet::FULL] == (0, Vec::new())`
|
||||||
}
|
fn score(
|
||||||
|
dictionary: &[(String, LetterSet)],
|
||||||
|
bests: &mut [(u8, HashSet<String>)],
|
||||||
|
done_letters: LetterSet,
|
||||||
|
length: u8,
|
||||||
|
) -> u8 {
|
||||||
|
assert!(length <= MAX_SOLUTION_LENGTH);
|
||||||
|
|
||||||
|
let known = bests[done_letters.to_index()].0;
|
||||||
|
if known < u8::MAX {
|
||||||
|
return known;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, (word, ls)) in dictionary
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, (_, ls))| !ls.difference(done_letters).is_empty())
|
||||||
|
{
|
||||||
|
let keep_letters = done_letters.union(*ls);
|
||||||
|
let keep_length = length.saturating_add(u8::try_from(word.len()).unwrap());
|
||||||
|
|
||||||
|
if keep_length > MAX_SOLUTION_LENGTH {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let keep = score(dictionary, bests, keep_letters, keep_length);
|
||||||
|
|
||||||
|
let existing = bests[done_letters.to_index()].0;
|
||||||
|
if keep < existing {
|
||||||
|
bests[done_letters.to_index()].0 = keep;
|
||||||
|
bests[done_letters.to_index()].1.insert(word.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
score(dictionary, &mut bests, target);
|
bests[done_letters.to_index()].0
|
||||||
|
|
||||||
bests.get(&target).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: perhaps bottom up? (pick or no pick) (real potential!)
|
|
||||||
// the problem is that i'm not *improving* scores, i use the best one immediately
|
|
||||||
// length doesn't include spaces. i think that's okay
|
|
||||||
// assumes that `bests[LetterSet::EMPTY] == (0, Vec::new())`
|
|
||||||
fn score(
|
|
||||||
dictionary: &[(String, LetterSet)],
|
|
||||||
bests: &mut HashMap<LetterSet, (u16, Vec<String>)>,
|
|
||||||
remaining: LetterSet,
|
|
||||||
) -> u16 {
|
|
||||||
if let Some(min) = bests.get(&remaining) {
|
|
||||||
return min.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some((word, ls, score)) = dictionary
|
|
||||||
.iter()
|
|
||||||
.filter(|(_, ls)| {
|
|
||||||
ls.intersection(remaining.negation()).is_empty()
|
|
||||||
|| !remaining.intersection(*ls).is_empty()
|
|
||||||
//&& ls.intersection(remaining.negation()).len() <= 1
|
|
||||||
})
|
|
||||||
.map(|(word, ls)| {
|
|
||||||
let rest_score = score(dictionary, bests, remaining.difference(*ls));
|
|
||||||
(
|
|
||||||
word,
|
|
||||||
ls,
|
|
||||||
(u16::try_from(word.len()).unwrap()).saturating_add(rest_score),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.min_by_key(|(_, _, score)| *score)
|
|
||||||
else {
|
|
||||||
return u16::MAX;
|
|
||||||
};
|
|
||||||
|
|
||||||
if score == u16::MAX {
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
|
|
||||||
let remaining_after_word = remaining.difference(*ls);
|
|
||||||
let (_score, path) = bests
|
|
||||||
.get(&remaining_after_word)
|
|
||||||
.expect("we already inserted this");
|
|
||||||
let mut path = path.clone();
|
|
||||||
path.push(word.clone());
|
|
||||||
bests.insert(remaining, (score, path));
|
|
||||||
score
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue