#![doc = include_str!("../README.md")]
#![allow(clippy::too_many_lines)]
use std::cmp::{max, min, Ordering};
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
use std::fmt::Debug;
use std::hash::Hash;
use std::iter::{empty, once, Rev};
use std::ops::RangeInclusive;
use either::Either;
use internal_util::{
comb,
fact_div,
graph_traverse,
map_reduce,
peek,
pop,
CustomInto,
FrozenMap,
FrozenSet,
};
use itertools::Itertools;
use typed_arena::Arena;
mod internal_util;
pub mod util;
type SuperCell<'arena, 'cell, T> = &'arena FrozenSet<&'cell T>;
pub trait Cell: Hash + Eq + Debug {}
impl<T: Hash + Eq + Debug + ?Sized> Cell for T {
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct BoardInfo {
pub total_cells: usize,
pub total_mines: usize,
}
pub type Error = &'static str;
pub fn solve<'cell, T: Cell + ?Sized>(
rules: &[Rule<'cell, T>],
mine_prevalence: impl CustomInto<Either<BoardInfo, f64>>,
other_tag: impl Into<&'cell T>,
) -> Result<HashMap<&'cell T, f64>, Error> {
let rule_arena = Arena::new();
let super_cell_arena = Arena::new();
let ruleset_arena = Arena::new();
let permutation_arena = Arena::new();
let permutation_set_arena = Arena::new();
let super_cell_group_arena = Arena::new();
let mine_prevalence = mine_prevalence.into();
let (internal_rules, all_cells) = condense_supercells(
rules,
&rule_arena,
&super_cell_arena,
&super_cell_group_arena,
)?;
let mut reduced = reduce_rules(
&internal_rules,
&rule_arena,
&ruleset_arena,
&super_cell_group_arena,
);
let mut determined = reduced
.iter()
.filter(|r| r.is_trivial())
.cloned()
.collect::<HashSet<_>>();
reduced.retain(|r| !r.is_trivial());
let mut fronts = permute_and_interfere(
ruleset_arena.alloc(reduced),
&rule_arena,
&ruleset_arena,
&permutation_arena,
&permutation_set_arena,
&super_cell_group_arena,
)?
.split_fronts(&ruleset_arena, &permutation_arena, &permutation_set_arena);
determined.extend(
fronts
.iter()
.filter(|f| f.is_trivial())
.map(PermutedRuleset::trivial_rule),
);
fronts.retain(|f| !f.is_trivial());
let mut stats = fronts
.into_iter()
.map(|f| enumerate_front(f))
.collect::<Result<Vec<_>, _>>()?;
stats.extend(determined.into_iter().map(|r| r.tally()));
let cell_probs = cell_probabilities(stats, mine_prevalence, all_cells)?;
let cells = expand_cells::<'_, 'cell, _>(cell_probs, other_tag.into()).collect();
Ok(cells)
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Rule<'cell, T: Cell + ?Sized> {
num_mines: usize,
cells: FrozenSet<&'cell T>,
}
impl<'cell, T: Cell + ?Sized> Rule<'cell, T> {
pub fn new(num_mines: usize, cells: impl IntoIterator<Item = &'cell T>) -> Self {
Self {
num_mines,
cells: cells.into_iter().collect(),
}
}
fn condensed<'arena>(
&self,
rule_supercells_map: &HashMap<
&Self,
&'arena FrozenSet<SuperCell<'arena, 'cell, T>>,
>,
rule_arena: &'arena Arena<InternalRule<'arena, 'cell, T>>,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) -> Result<&'arena InternalRule<'arena, 'cell, T>, Error> {
InternalRule::new(
self.num_mines,
rule_supercells_map
.get(self)
.copied()
.unwrap_or_else(|| &*super_cell_group_arena.alloc(FrozenSet::new())),
self.cells.len(),
)
.map(|rule| &*rule_arena.alloc(rule))
}
}
#[derive(Copy, Debug, PartialEq, Eq, Hash)]
struct InternalRule<'arena, 'cell: 'arena, T: Cell + ?Sized> {
num_mines: usize,
num_cells: usize,
super_cells: &'arena FrozenSet<SuperCell<'arena, 'cell, T>>,
}
impl<'arena, 'cell: 'arena, T: Cell + ?Sized> Clone for InternalRule<'arena, 'cell, T> {
fn clone(&self) -> Self {
Self {
num_mines: self.num_mines,
num_cells: self.num_cells,
super_cells: self.super_cells,
}
}
}
impl<'arena, 'cell: 'arena, T: Cell + ?Sized> InternalRule<'arena, 'cell, T> {
pub fn new(
num_mines: usize,
super_cells: &'arena FrozenSet<SuperCell<'arena, 'cell, T>>,
num_cells: usize,
) -> Result<Self, Error> {
if num_mines <= num_cells {
Ok(Self {
num_mines,
num_cells,
super_cells,
})
} else {
Err("Rule with more mines than cells")
}
}
pub fn decompose(
&self,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) -> Box<dyn Iterator<Item = Self> + '_> {
if self.num_mines == 0 || self.num_mines == self.num_cells {
Box::new(self.super_cells.iter().map(|&super_cell| {
let size = super_cell.len();
Self::new(
if self.num_mines > 0 { size } else { 0 },
super_cell_group_arena.alloc([super_cell].into()),
size,
)
.expect("Decomposing a rule should always be valid")
}))
} else {
Box::new(once(self.clone()))
}
}
pub fn subtract(
&self,
other: &Self,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) -> Self {
Self::new(
self.num_mines - other.num_mines,
super_cell_group_arena.alloc(self.super_cells - &other.super_cells),
self.num_cells - other.num_cells,
)
.expect("Subtracting a rule should always result in a valid rule")
}
pub fn permute(&self) -> impl Iterator<Item = Permutation<'arena, 'cell, T>> + '_ {
fn permute<'arena, 'cell: 'arena, T: Cell + ?Sized>(
count: usize,
mut cells: VecDeque<&'arena FrozenSet<&'cell T>>,
permu: HashSet<(&'arena FrozenSet<&'cell T>, usize)>,
) -> Box<dyn Iterator<Item = Permutation<'arena, 'cell, T>> + 'arena> {
if count == 0 {
Box::new(once(Permutation::new(
permu
.union(&cells.iter().map(|&cell| (cell, 0)).collect())
.cloned(),
)))
} else {
let remaining_size = cells.iter().map(|cell| cell.len()).sum::<usize>();
if remaining_size == count {
Box::new(once(Permutation::new(
permu
.union(
&cells.iter().map(|&cell| (cell, cell.len())).collect(),
)
.cloned(),
)))
} else if remaining_size >= count {
struct Iter<'a, 'arena: 'a, 'cell: 'arena, T: Cell + ?Sized> {
cell: &'arena FrozenSet<&'cell T>,
multiplicity: usize,
multiplicity_iter: Rev<RangeInclusive<usize>>,
inner: Box<
dyn Iterator<Item = Permutation<'arena, 'cell, T>> + 'a,
>,
cells: VecDeque<&'arena FrozenSet<&'cell T>>,
count: usize,
permu: HashSet<(&'arena FrozenSet<&'cell T>, usize)>,
}
impl<'a, 'arena: 'a, 'cell: 'arena, T: Cell + ?Sized> Iterator
for Iter<'a, 'arena, 'cell, T>
{
type Item = Permutation<'arena, 'cell, T>;
fn next(&mut self) -> Option<Self::Item> {
match self.inner.next() {
Some(permutation) => Some(permutation),
None => {
self.multiplicity =
self.multiplicity_iter.next()?;
self.inner = permute(
self.count - self.multiplicity,
self.cells.clone(),
self.permu
.union(
&[(self.cell, self.multiplicity)]
.into(),
)
.cloned()
.collect(),
);
self.next()
},
}
}
}
let cell = cells.pop_front().expect(concat!(
"count >= 1, remaining_size >= count, so",
" cells must not be empty",
));
Box::new(Iter {
cell,
multiplicity: 0,
multiplicity_iter: (0..=min(count, cell.len())).rev(),
inner: Box::new(empty()),
cells,
count,
permu,
})
} else {
Box::new(empty())
}
}
}
let cells = self.super_cells.iter().cloned().collect();
permute(self.num_mines, cells, HashSet::new())
.collect_vec()
.into_iter()
}
pub fn is_subule_of(&self, other: &Self) -> bool {
self.super_cells.is_subset(&other.super_cells)
}
pub fn is_trivial(&self) -> bool {
self.super_cells.len() == 1
}
pub fn tally(&self) -> FrontTally<'arena, 'cell, T> {
FrontTally::from_rule(self)
}
}
fn condense_supercells<'arena, 'cell: 'arena, T: Cell + ?Sized>(
rules: &[Rule<'cell, T>],
rule_arena: &'arena Arena<InternalRule<'arena, 'cell, T>>,
super_cell_arena: &'arena Arena<FrozenSet<&'cell T>>,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) -> Result<
(
Vec<&'arena InternalRule<'arena, 'cell, T>>,
Vec<&'arena FrozenSet<&'cell T>>,
),
Error,
> {
let cell_rules_map = map_reduce(
rules.iter(),
|rule| rule.cells.iter().map(move |&cell| (cell, rule)),
|rules| rules.into_iter().collect::<FrozenSet<_>>(),
);
let rules_supercell_map = map_reduce(
cell_rules_map.into_iter(),
|(cell, rules)| [(rules, cell)].into_iter(),
|cells| &*super_cell_arena.alloc(cells.into_iter().collect::<FrozenSet<_>>()),
);
let rule_supercells_map = map_reduce(
rules_supercell_map.iter(),
|(rules, cell)| {
rules
.iter()
.map(|&rule| (rule, &**cell))
.collect_vec()
.into_iter()
},
|cells| {
&*super_cell_group_arena.alloc(cells.into_iter().collect::<FrozenSet<_>>())
},
);
Ok((
rules
.iter()
.map(|rule| {
rule.condensed(&rule_supercells_map, rule_arena, super_cell_group_arena)
})
.collect::<Result<Vec<_>, Error>>()?,
rules_supercell_map.into_values().collect(),
))
}
fn reduce_rules<'arena, 'cell: 'arena, T: Cell + ?Sized>(
rules: &[&'arena InternalRule<'arena, 'cell, T>],
rule_arena: &'arena Arena<InternalRule<'arena, 'cell, T>>,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) -> HashSet<&'arena InternalRule<'arena, 'cell, T>> {
let mut reducer = RuleReducer::new();
reducer.add_rules(
rules.iter().copied(),
rule_arena,
ruleset_arena,
super_cell_group_arena,
);
reducer.reduce_all(rule_arena, ruleset_arena, super_cell_group_arena)
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct Reduceable<'arena, 'cell: 'arena, T: Cell + ?Sized> {
super_rule: &'arena InternalRule<'arena, 'cell, T>,
sub_rule: &'arena InternalRule<'arena, 'cell, T>,
}
impl<'arena, 'cell: 'arena, T: Cell + ?Sized> Reduceable<'arena, 'cell, T> {
pub fn new(
super_rule: &'arena InternalRule<'arena, 'cell, T>,
sub_rule: &'arena InternalRule<'arena, 'cell, T>,
) -> Self {
Self {
super_rule,
sub_rule,
}
}
pub fn metric(&self) -> (usize, usize, usize) {
let num_reduced_cells = self.super_rule.num_cells - self.sub_rule.num_cells;
let num_reduced_mines = self.super_rule.num_mines - self.sub_rule.num_mines;
(
self.super_rule.num_cells,
self.sub_rule.num_cells,
num_reduced_mines.abs_diff(num_reduced_cells / 2),
)
}
pub fn reduce(
&self,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) -> InternalRule<'arena, 'cell, T> {
self.super_rule
.subtract(&self.sub_rule, super_cell_group_arena)
}
pub fn contained_within(
&self,
rules: &HashSet<&'arena InternalRule<'arena, 'cell, T>>,
) -> bool {
rules.contains(&self.super_rule) && rules.contains(&self.sub_rule)
}
}
impl<T: Cell + ?Sized> Ord for Reduceable<'_, '_, T> {
fn cmp(&self, other: &Self) -> Ordering {
self.metric().cmp(&other.metric())
}
}
impl<T: Cell + ?Sized> PartialOrd for Reduceable<'_, '_, T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, PartialEq, Eq)]
struct CellRulesMap<'arena, 'cell: 'arena, T: Cell + ?Sized> {
map: HashMap<
SuperCell<'arena, 'cell, T>,
&'arena mut HashSet<&'arena InternalRule<'arena, 'cell, T>>,
>,
rules: HashSet<&'arena InternalRule<'arena, 'cell, T>>,
}
impl<'arena, 'cell: 'arena, T: Cell + ?Sized> CellRulesMap<'arena, 'cell, T> {
pub fn new() -> Self {
Self {
map: HashMap::new(),
rules: HashSet::new(),
}
}
pub fn add_rules(
&mut self,
rules: impl Iterator<Item = &'arena InternalRule<'arena, 'cell, T>>,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
) {
for rule in rules {
self.add_rule(rule, ruleset_arena);
}
}
pub fn add_rule(
&mut self,
rule: &'arena InternalRule<'arena, 'cell, T>,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
) {
for super_cell in rule.super_cells.iter() {
(*self
.map
.entry(super_cell)
.or_insert_with(|| ruleset_arena.alloc(HashSet::new())))
.insert(rule);
}
self.rules.insert(rule);
}
pub fn remove_rule(&mut self, rule: &InternalRule<'arena, 'cell, T>) {
for super_cell in rule.super_cells.iter() {
self.map
.get_mut(super_cell)
.expect("Attempted to remove a rule that was not present")
.remove(rule);
}
self.rules.remove(rule);
}
pub fn overlapping_rules(
&self,
rule: &InternalRule<'arena, 'cell, T>,
) -> HashSet<&'arena InternalRule<'arena, 'cell, T>> {
let empty = HashSet::new();
let mut rules = rule
.super_cells
.iter()
.flat_map(|&super_cell| {
self.map
.get(super_cell)
.map(|val| &**val)
.map_or_else(|| empty.iter(), |map| map.iter())
.copied()
})
.collect::<HashSet<_>>();
rules.remove(rule);
rules
}
pub fn interference_edges(
&self,
) -> HashSet<(
&'arena InternalRule<'arena, 'cell, T>,
&'arena InternalRule<'arena, 'cell, T>,
)> {
self.rules
.iter()
.flat_map(|&rule| {
self.overlapping_rules(rule)
.into_iter()
.map(move |overlapping| (rule, overlapping))
})
.collect()
}
pub fn partition(
&self,
) -> HashSet<FrozenSet<&'arena InternalRule<'arena, 'cell, T>>> {
let mut related_rules: HashMap<_, _> = self
.rules
.iter()
.map(|&rule| (rule, self.overlapping_rules(rule)))
.collect();
let mut partitions = HashSet::new();
while !related_rules.is_empty() {
let start = peek(related_rules.keys());
let partition = graph_traverse(&related_rules, start);
for rule in &partition {
related_rules.remove(rule);
}
partitions.insert(partition.into());
}
partitions
}
pub fn super_cells(&self) -> FrozenSet<SuperCell<'arena, 'cell, T>> {
self.map.keys().cloned().collect()
}
}
#[derive(Debug)]
struct RuleReducer<'arena, 'cell: 'arena, T: Cell + ?Sized> {
active_rules: HashSet<&'arena InternalRule<'arena, 'cell, T>>,
cell_rules_map: CellRulesMap<'arena, 'cell, T>,
candidate_reductions: BinaryHeap<Reduceable<'arena, 'cell, T>>,
}
impl<'arena, 'cell: 'arena, T: Cell + ?Sized> RuleReducer<'arena, 'cell, T> {
pub fn new() -> Self {
Self {
active_rules: HashSet::new(),
cell_rules_map: CellRulesMap::new(),
candidate_reductions: BinaryHeap::new(),
}
}
pub fn add_rules<'a>(
&mut self,
rules: impl Iterator<Item = &'a InternalRule<'arena, 'cell, T>>,
rule_arena: &'arena Arena<InternalRule<'arena, 'cell, T>>,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) where
'arena: 'a,
{
for rule in rules {
self.add_rule(rule, rule_arena, ruleset_arena, super_cell_group_arena);
}
}
pub fn add_rule(
&mut self,
rule: &InternalRule<'arena, 'cell, T>,
rule_arena: &'arena Arena<InternalRule<'arena, 'cell, T>>,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) {
for base_rule in rule.decompose(super_cell_group_arena) {
self.add_base_rule(rule_arena.alloc(base_rule), ruleset_arena);
}
}
pub fn add_base_rule(
&mut self,
rule: &'arena InternalRule<'arena, 'cell, T>,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
) {
self.active_rules.insert(rule);
self.cell_rules_map.add_rule(rule, ruleset_arena);
self.update_reduceables(rule);
}
pub fn add_reduceable(&mut self, reduction: Reduceable<'arena, 'cell, T>) {
self.candidate_reductions.push(reduction);
}
pub fn update_reduceables(&mut self, rule: &'arena InternalRule<'arena, 'cell, T>) {
for overlapping in self.cell_rules_map.overlapping_rules(rule) {
if overlapping.is_subule_of(rule) {
self.add_reduceable(Reduceable::new(rule, overlapping));
} else if rule.is_subule_of(&overlapping) {
self.add_reduceable(Reduceable::new(overlapping, rule));
}
}
}
pub fn remove_rule(&mut self, rule: &InternalRule<'arena, 'cell, T>) {
self.active_rules.remove(rule);
self.cell_rules_map.remove_rule(rule);
}
pub fn get_next_reduction(&mut self) -> Option<Reduceable<'arena, 'cell, T>> {
while let Some(reduction) = self.candidate_reductions.pop() {
if reduction.contained_within(&self.active_rules) {
return Some(reduction);
}
}
None
}
pub fn reduce(
&mut self,
reduction: Reduceable<'arena, 'cell, T>,
rule_arena: &'arena Arena<InternalRule<'arena, 'cell, T>>,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) {
let reduced_rule = reduction.reduce(super_cell_group_arena);
self.remove_rule(&reduction.super_rule);
self.add_rule(
&reduced_rule,
rule_arena,
ruleset_arena,
super_cell_group_arena,
);
}
pub fn reduce_all(
mut self,
rule_arena: &'arena Arena<InternalRule<'arena, 'cell, T>>,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) -> HashSet<&'arena InternalRule<'arena, 'cell, T>> {
while let Some(reduction) = self.get_next_reduction() {
self.reduce(reduction, rule_arena, ruleset_arena, super_cell_group_arena);
}
self.active_rules
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct Permutation<'arena, 'cell: 'arena, T: Cell + ?Sized> {
mapping: FrozenMap<SuperCell<'arena, 'cell, T>, usize>,
}
impl<'arena, 'cell: 'arena, T: Cell + ?Sized> Permutation<'arena, 'cell, T> {
pub fn new(
mapping: impl IntoIterator<Item = (SuperCell<'arena, 'cell, T>, usize)>,
) -> Self {
Self {
mapping: mapping.into_iter().collect(),
}
}
pub fn subset(
&self,
sub_cells: impl Iterator<Item = SuperCell<'arena, 'cell, T>>,
) -> Self {
Self::new(sub_cells.map(|cell| {
let mapped = self.mapping[&cell];
(cell, mapped)
}))
}
pub fn is_compatible(&self, other: &Self) -> bool {
let overlap = self
.cells()
.intersection(&other.cells())
.cloned()
.collect::<HashSet<_>>();
self.subset(overlap.iter().cloned()) == other.subset(overlap.into_iter())
}
pub fn combine(&self, other: &Self) -> Self {
assert!(self
.mapping
.iter()
.filter(|(cell, _)| other.mapping.contains_key(*cell))
.all(|(cell, value)| other.mapping[cell] == *value));
let mut map = self.mapping.clone().into_inner();
for (&k, &v) in other.mapping.iter() {
map.insert(k, v);
}
Self::new(map)
}
pub fn k(&self) -> usize {
self.mapping.values().sum::<usize>()
}
pub fn cells(&self) -> HashSet<SuperCell<'arena, 'cell, T>> {
self.mapping.keys().copied().collect()
}
pub fn multiplicity(&self) -> usize {
self.mapping
.iter()
.map(|(&super_cell, &k)| comb(super_cell.len(), k))
.product::<usize>()
}
}
#[derive(Debug, PartialEq, Eq)]
struct PermutationSet<'arena, 'cell: 'arena, T: Cell + ?Sized> {
super_cells: &'arena FrozenSet<SuperCell<'arena, 'cell, T>>,
k: usize,
permutations: HashSet<&'arena Permutation<'arena, 'cell, T>>,
constrained: bool,
}
impl<'arena, 'cell: 'arena, T: Cell + ?Sized> Clone
for PermutationSet<'arena, 'cell, T>
{
fn clone(&self) -> Self {
Self {
super_cells: self.super_cells,
k: self.k,
permutations: self.permutations.clone(),
constrained: self.constrained,
}
}
}
impl<'arena, 'cell: 'arena, T: Cell + ?Sized> PermutationSet<'arena, 'cell, T> {
pub fn new(
super_cells: &'arena FrozenSet<SuperCell<'arena, 'cell, T>>,
k: usize,
permutations: impl IntoIterator<Item = &'arena Permutation<'arena, 'cell, T>>,
) -> Self {
Self {
super_cells,
k,
permutations: permutations.into_iter().collect(),
constrained: false,
}
}
pub fn from_rule(
rule: &InternalRule<'arena, 'cell, T>,
permutation_arena: &'arena Arena<Permutation<'arena, 'cell, T>>,
) -> Self {
Self::new(
rule.super_cells,
rule.num_mines,
rule.permute().map(|perm| &*permutation_arena.alloc(perm)),
)
}
pub fn to_rule(&self) -> InternalRule<'arena, 'cell, T> {
InternalRule::new(
self.k,
self.super_cells,
self.super_cells
.iter()
.map(|super_cell| super_cell.len())
.sum::<usize>(),
)
.expect("Should be impossible to create an invalid rule from a PermutationSet")
}
pub fn iter(
&self,
) -> impl Iterator<Item = &'arena Permutation<'arena, 'cell, T>> + '_ {
self.permutations.iter().copied()
}
pub fn contains(&self, p: &Permutation<'arena, 'cell, T>) -> bool {
self.permutations.contains(p)
}
pub fn remove(&mut self, permu: &Permutation<'arena, 'cell, T>) {
self.permutations.remove(permu);
self.constrained = true;
}
pub fn is_empty(&self) -> bool {
self.permutations.is_empty()
}
pub fn compatible(&self, permu: &Permutation<'arena, 'cell, T>) -> Self {
Self::new(
self.super_cells,
self.k,
self.permutations
.iter()
.filter(|p| p.is_compatible(permu))
.cloned(),
)
}
pub fn subset(
&self,
cell_subset: &'arena FrozenSet<SuperCell<'arena, 'cell, T>>,
permutation_arena: &'arena Arena<Permutation<'arena, 'cell, T>>,
) -> Option<Self> {
let permu_subset = self
.permutations
.iter()
.map(|p| &*permutation_arena.alloc(p.subset(cell_subset.iter().cloned())))
.collect::<HashSet<_>>();
let mut k_sub = permu_subset.iter().map(|p| p.k()).collect::<HashSet<_>>();
if k_sub.len() == 1 {
Some(Self::new(
cell_subset,
pop(&mut k_sub).unwrap(),
permu_subset,
))
} else {
None
}
}
pub fn decompose(
&self,
permutation_arena: &'arena Arena<Permutation<'arena, 'cell, T>>,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) -> Vec<Self> {
if self.constrained {
self.force_decompose(1, permutation_arena, super_cell_group_arena)
} else {
vec![self.clone()]
}
}
fn force_decompose(
&self,
k_floor: usize,
permutation_arena: &'arena Arena<Permutation<'arena, 'cell, T>>,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) -> Vec<Self> {
for k in k_floor..=(self.super_cells.len() / 2) {
for cell_subset in self.super_cells.iter().combinations(k) {
let cell_subset = &*super_cell_group_arena
.alloc(cell_subset.into_iter().cloned().collect::<FrozenSet<_>>());
let Some((
permu_subset,
permu_remainder,
)) = self.split(cell_subset, permutation_arena, super_cell_group_arena)
else {
continue;
};
let mut divisors = vec![permu_subset];
divisors.extend(permu_remainder.force_decompose(
k,
permutation_arena,
super_cell_group_arena,
));
return divisors;
}
}
vec![self.clone()]
}
fn split(
&self,
cell_subset: &'arena FrozenSet<SuperCell<'arena, 'cell, T>>,
permutation_arena: &'arena Arena<Permutation<'arena, 'cell, T>>,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) -> Option<(Self, Self)> {
let cell_remainder = super_cell_group_arena.alloc(
self.super_cells
.difference(cell_subset)
.cloned()
.collect::<FrozenSet<_>>(),
);
let permu_subset = self.subset(cell_subset, permutation_arena)?;
let mut permu_remainders = map_reduce(
self.permutations.iter().copied(),
|p| [(p.subset(cell_subset.iter().copied()), p)].into_iter(),
|pv| {
pv.into_iter()
.map(|p| {
permutation_arena
.alloc(p.subset(cell_remainder.iter().copied()))
})
.collect::<FrozenSet<_>>()
},
)
.into_values()
.collect::<HashSet<_>>();
if permu_remainders.len() == 1 {
let permu_remainders = pop(&mut permu_remainders).unwrap();
let other_k = permu_subset.k;
Some((
permu_subset,
Self::new(
cell_remainder,
self.k - other_k,
permu_remainders.into_iter().map(|p| &*p),
),
))
} else {
None
}
}
}
#[derive(Debug, PartialEq, Eq)]
struct PermutedRuleset<'arena, 'cell: 'arena, T: Cell + ?Sized> {
rules: &'arena mut HashSet<&'arena InternalRule<'arena, 'cell, T>>,
cell_rules_map: CellRulesMap<'arena, 'cell, T>,
super_cells: FrozenSet<SuperCell<'arena, 'cell, T>>,
permu_map: HashMap<
&'arena InternalRule<'arena, 'cell, T>,
&'arena mut PermutationSet<'arena, 'cell, T>,
>,
}
impl<'arena, 'cell: 'arena, T: Cell + ?Sized> PermutedRuleset<'arena, 'cell, T> {
pub fn new(
rules: &'arena mut HashSet<&'arena InternalRule<'arena, 'cell, T>>,
permu_map: Option<
HashMap<
&'arena InternalRule<'arena, 'cell, T>,
&'arena mut PermutationSet<'arena, 'cell, T>,
>,
>,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
permutation_arena: &'arena Arena<Permutation<'arena, 'cell, T>>,
permutation_set_arena: &'arena Arena<PermutationSet<'arena, 'cell, T>>,
) -> Self {
let mut cell_rules_map = CellRulesMap::new();
cell_rules_map.add_rules(rules.iter().cloned(), ruleset_arena);
let super_cells = cell_rules_map.super_cells();
Self {
cell_rules_map,
super_cells,
permu_map: match permu_map {
Some(permu_map) => {
permu_map
},
None => {
rules
.iter()
.map(|&rule| {
(
rule,
permutation_set_arena.alloc(PermutationSet::from_rule(
rule,
permutation_arena,
)),
)
})
.collect()
},
},
rules,
}
}
pub fn cross_eliminate(&mut self) -> Result<(), Error> {
let mut interferences = self.cell_rules_map.interference_edges();
while let Some((rule, overlapping)) = pop(&mut interferences) {
let mut changed = false;
for permu in self.permu_map[&rule].iter().collect_vec() {
if self.permu_map[&overlapping].compatible(&permu).is_empty() {
self.permu_map
.get_mut(&rule)
.expect("self.permu_map[rule] has already been accessed")
.remove(&permu);
changed = true;
}
}
if self.permu_map[&rule].is_empty() {
return Err(
"Rule is constrained such that it has no valid mine permutations"
);
} else if changed {
interferences.extend(
self.cell_rules_map
.overlapping_rules(&rule)
.into_iter()
.map(|other| (other, rule)),
);
}
}
Ok(())
}
pub fn rereduce(
&mut self,
rule_arena: &'arena Arena<InternalRule<'arena, 'cell, T>>,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
permutation_arena: &'arena Arena<Permutation<'arena, 'cell, T>>,
permutation_set_arena: &'arena Arena<PermutationSet<'arena, 'cell, T>>,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) {
let mut superseded_rules = HashSet::new();
let mut decompositions = HashMap::new();
for (&rule, permu_set) in self.permu_map.iter() {
let decomp = permu_set.decompose(permutation_arena, super_cell_group_arena);
if decomp.len() > 1 {
superseded_rules.insert(rule);
decompositions
.extend(decomp.into_iter().map(|dc| (dc.super_cells, dc)));
}
}
for rule in superseded_rules {
self.remove_rule(rule);
}
for permu_set in decompositions.into_values() {
self.add_permu_set(
permutation_set_arena.alloc(permu_set),
rule_arena,
ruleset_arena,
);
}
}
pub fn remove_rule(&mut self, rule: &InternalRule<'arena, 'cell, T>) {
self.rules.remove(rule);
self.cell_rules_map.remove_rule(rule);
self.permu_map.remove(rule);
}
pub fn add_permu_set(
&mut self,
permu_set: &'arena mut PermutationSet<'arena, 'cell, T>,
rule_arena: &'arena Arena<InternalRule<'arena, 'cell, T>>,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
) {
let rule = rule_arena.alloc(permu_set.to_rule());
self.rules.insert(rule);
self.cell_rules_map.add_rule(rule, ruleset_arena);
self.permu_map.insert(rule, permu_set);
}
pub fn filter(
&mut self,
rule_subset: &'arena mut HashSet<&'arena InternalRule<'arena, 'cell, T>>,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
permutation_arena: &'arena Arena<Permutation<'arena, 'cell, T>>,
permutation_set_arena: &'arena Arena<PermutationSet<'arena, 'cell, T>>,
) -> Self {
let permu_map = rule_subset
.iter()
.map(|&rule| {
self.permu_map
.remove_entry(rule)
.expect("Entry should be present")
})
.collect();
Self::new(
rule_subset,
Some(permu_map),
ruleset_arena,
permutation_arena,
permutation_set_arena,
)
}
pub fn split_fronts(
mut self,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
permutation_arena: &'arena Arena<Permutation<'arena, 'cell, T>>,
permutation_set_arena: &'arena Arena<PermutationSet<'arena, 'cell, T>>,
) -> Vec<Self> {
self.cell_rules_map
.partition()
.into_iter()
.map(|rule_subset| {
self.filter(
ruleset_arena.alloc(rule_subset.iter().cloned().collect()),
ruleset_arena,
permutation_arena,
permutation_set_arena,
)
})
.collect()
}
pub fn is_trivial(&self) -> bool {
self.rules.len() == 1
}
pub fn trivial_rule(&self) -> &'arena InternalRule<'arena, 'cell, T> {
assert!(self.is_trivial());
let singleton = peek(self.rules.iter().cloned());
assert!(singleton.is_trivial());
singleton
}
pub fn enumerate(
&self,
) -> Box<dyn Iterator<Item = Permutation<'arena, 'cell, T>> + '_> {
EnumerationState::new(self).enumerate()
}
}
fn permute_and_interfere<'arena, 'cell: 'arena, T: Cell + ?Sized>(
rules: &'arena mut HashSet<&'arena InternalRule<'arena, 'cell, T>>,
rule_arena: &'arena Arena<InternalRule<'arena, 'cell, T>>,
ruleset_arena: &'arena Arena<HashSet<&'arena InternalRule<'arena, 'cell, T>>>,
permutation_arena: &'arena Arena<Permutation<'arena, 'cell, T>>,
permutation_set_arena: &'arena Arena<PermutationSet<'arena, 'cell, T>>,
super_cell_group_arena: &'arena Arena<FrozenSet<SuperCell<'arena, 'cell, T>>>,
) -> Result<PermutedRuleset<'arena, 'cell, T>, Error> {
let mut permuted_ruleset = PermutedRuleset::new(
rules,
None,
ruleset_arena,
permutation_arena,
permutation_set_arena,
);
permuted_ruleset.cross_eliminate()?;
permuted_ruleset.rereduce(
rule_arena,
ruleset_arena,
permutation_arena,
permutation_set_arena,
super_cell_group_arena,
);
Ok(permuted_ruleset)
}
#[derive(Debug, PartialEq, Eq)]
struct EnumerationState<'rules, 'arena, 'cell: 'arena, T: Cell + ?Sized> {
fixed: HashSet<&'arena Permutation<'arena, 'cell, T>>,
ruleset: &'rules PermutedRuleset<'arena, 'cell, T>,
free: HashMap<
&'arena InternalRule<'arena, 'cell, T>,
HashSet<&'arena Permutation<'arena, 'cell, T>>,
>,
compatible_rule_index: HashMap<
(
&'arena Permutation<'arena, 'cell, T>,
&'arena InternalRule<'arena, 'cell, T>,
),
PermutationSet<'arena, 'cell, T>,
>,
}
impl<'rules, 'arena, 'cell: 'arena, T: Cell + ?Sized> Clone
for EnumerationState<'rules, 'arena, 'cell, T>
{
fn clone(&self) -> Self {
Self {
fixed: self.fixed.clone(),
ruleset: self.ruleset,
free: self.free.clone(),
compatible_rule_index: self.compatible_rule_index.clone(),
}
}
}
impl<'rules, 'arena, 'cell: 'arena, T: Cell + ?Sized>
EnumerationState<'rules, 'arena, 'cell, T>
{
pub fn new(ruleset: &'rules PermutedRuleset<'arena, 'cell, T>) -> Self {
let mut rv = Self {
fixed: HashSet::new(),
ruleset,
free: ruleset
.permu_map
.iter()
.map(|(&rule, permu_set)| (rule, permu_set.iter().collect()))
.collect(),
compatible_rule_index: HashMap::new(),
};
rv.build_compatibility_index(&ruleset.permu_map);
rv
}
pub fn overlapping_rules(
&self,
rule: &InternalRule<'arena, 'cell, T>,
) -> HashSet<&'arena InternalRule<'arena, 'cell, T>> {
self.ruleset.cell_rules_map.overlapping_rules(rule)
}
pub fn build_compatibility_index(
&mut self,
map: &HashMap<
&'arena InternalRule<'arena, 'cell, T>,
&'arena mut PermutationSet<'arena, 'cell, T>,
>,
) {
for (rule, permu_set) in map.iter() {
for permu in permu_set.iter() {
for overlapping in self.overlapping_rules(rule) {
let v = map[&overlapping].compatible(permu);
self.compatible_rule_index.insert((permu, overlapping), v);
}
}
}
}
pub fn is_complete(&self) -> bool {
self.free.is_empty()
}
pub fn into_iter(
mut self,
) -> impl Iterator<Item = EnumerationState<'rules, 'arena, 'cell, T>> + 'rules {
let state = self.clone();
let rule = peek(state.free.keys().copied());
self.free
.remove_entry(rule)
.expect("Rule was taken out of keys")
.1
.into_iter()
.filter_map(move |permu| state.propagate(&rule, permu))
}
pub fn propagate(
&self,
rule: &'arena InternalRule<'arena, 'cell, T>,
permu: &'arena Permutation<'arena, 'cell, T>,
) -> Option<Self> {
let mut state = self.clone();
state.propagate_in_place(rule, permu)?;
Some(state)
}
fn propagate_in_place(
&mut self,
rule: &'arena InternalRule<'arena, 'cell, T>,
permu: &'arena Permutation<'arena, 'cell, T>,
) -> Option<()> {
self.fixed.insert(permu);
self.free.remove(rule)?;
let mut cascades = Vec::new();
for related_rule in self
.overlapping_rules(rule)
.into_iter()
.filter(|r| self.free.contains_key(r))
.collect_vec()
{
let allowed_permus =
self.compatible_rule_index.get(&(permu, related_rule))?;
self.free
.get_mut(related_rule)?
.retain(|p| allowed_permus.contains(p));
let linked_permus = self.free.get(&related_rule)?;
if linked_permus.is_empty() {
return None;
} else if linked_permus.len() == 1 {
cascades.push((related_rule, peek(linked_permus.iter().cloned())));
}
}
for (related_rule, constrained_permu) in cascades {
if self.free.contains_key(&related_rule) {
self.propagate_in_place(&related_rule, constrained_permu)?;
}
}
Some(())
}
pub fn mine_config(&self) -> Permutation<'arena, 'cell, T> {
self.fixed
.iter()
.fold(Permutation::new(empty()), |a, b| a.combine(b))
}
pub fn enumerate(
self,
) -> Box<dyn Iterator<Item = Permutation<'arena, 'cell, T>> + 'rules> {
if self.is_complete() {
Box::new(once(self.mine_config()))
} else {
Box::new(self.into_iter().flat_map(|s| s.enumerate()))
}
}
}
#[derive(Clone, Debug, PartialEq)]
struct FrontTally<'arena, 'cell: 'arena, T: Cell + ?Sized> {
subtallies: HashMap<usize, FrontSubtally<'arena, 'cell, T>>,
}
impl<'arena, 'cell: 'arena, T: Cell + ?Sized> FrontTally<'arena, 'cell, T> {
pub fn new(
data: impl Iterator<Item = (usize, FrontSubtally<'arena, 'cell, T>)>,
) -> Self {
Self {
subtallies: data.collect(),
}
}
pub fn tally(
&mut self,
front: PermutedRuleset<'arena, 'cell, T>,
) -> Result<(), Error> {
for config in front.enumerate() {
(self
.subtallies
.entry(config.k())
.or_insert_with(FrontSubtally::new))
.add(&config);
}
if self.subtallies.is_empty() {
return Err("Mine front has no possible configurations");
}
self.finalise();
Ok(())
}
pub fn finalise(&mut self) {
for subtally in self.subtallies.values_mut() {
subtally.finalise();
}
}
pub fn min_mines(&self) -> usize {
self.subtallies
.keys()
.copied()
.min()
.expect("Mine front has no possible configurations")
}
pub fn max_mines(&self) -> usize {
self.subtallies
.keys()
.copied()
.max()
.expect("Mine front has no possible configurations")
}
pub fn is_static(&self) -> bool {
self.subtallies.len() == 1
}
pub fn iter(
&self,
) -> impl Iterator<Item = (usize, &FrontSubtally<'arena, 'cell, T>)> + '_ {
self.subtallies.iter().map(|(&k, v)| (k, v))
}
pub fn normalise(&mut self) {
let total = self.subtallies.values().map(|s| s.total).sum::<f64>();
for subtally in self.subtallies.values_mut() {
subtally.total /= total;
}
}
pub fn collapse(
&mut self,
) -> HashMap<Either<SuperCell<'arena, 'cell, T>, UnchartedCell>, f64> {
self.normalise();
let collapsed = map_reduce(
self.subtallies.values(),
|subtally| subtally.collapse().collect_vec().into_iter(),
|values| values.into_iter().sum(),
);
collapsed
}
pub fn scale_weights(
&mut self,
scale_func: impl Fn(usize) -> Result<f64, Error>,
) -> Result<(), Error> {
for (&num_mines, subtally) in self.subtallies.iter_mut() {
subtally.total *= scale_func(num_mines)?;
}
Ok(())
}
pub fn update_weights(&mut self, weights: &HashMap<usize, f64>) {
for (&num_mines, subtally) in self.subtallies.iter_mut() {
subtally.total *= weights.get(&num_mines).copied().unwrap_or(0.0);
}
}
pub fn from_rule(rule: &InternalRule<'arena, 'cell, T>) -> Self {
assert!(rule.is_trivial());
Self::new(
[(
rule.num_mines,
FrontSubtally::from_data(
comb(rule.num_cells, rule.num_mines) as f64,
[(
Either::Left(peek(rule.super_cells.iter().cloned())),
rule.num_mines as f64,
)]
.into_iter(),
),
)]
.into_iter(),
)
}
pub fn for_other(
num_uncharted_cells: usize,
mine_totals: &HashMap<usize, f64>,
) -> Self {
let meta_cell = UnchartedCell::new(num_uncharted_cells);
Self::new(mine_totals.iter().map(|(&num_mines, &k)| {
(
num_mines,
FrontSubtally::from_data(
k,
[(Either::Right(meta_cell), num_mines as f64)].into_iter(),
),
)
}))
}
}
type CollapseResult<'arena, 'cell, T> =
(Either<SuperCell<'arena, 'cell, T>, UnchartedCell>, f64);
#[derive(Clone, Debug, PartialEq)]
struct FrontSubtally<'arena, 'cell: 'arena, T: Cell + ?Sized> {
total: f64,
tally: HashMap<Either<SuperCell<'arena, 'cell, T>, UnchartedCell>, f64>,
}
impl<'arena, 'cell: 'arena, T: Cell + ?Sized> FrontSubtally<'arena, 'cell, T> {
pub fn new() -> Self {
Self {
total: 0.0,
tally: HashMap::new(),
}
}
pub fn add(&mut self, config: &Permutation<'arena, 'cell, T>) {
let mult = config.multiplicity() as f64;
self.total += mult;
for (&super_cell, &n) in config.mapping.iter() {
*self.tally.entry(Either::Left(super_cell)).or_insert(0.0) +=
n as f64 * mult;
}
}
pub fn finalise(&mut self) {
self.tally.values_mut().for_each(|n| *n /= self.total);
}
pub fn collapse(
&self,
) -> impl Iterator<Item = CollapseResult<'arena, 'cell, T>> + '_ {
self.tally.iter().map(|(super_cell, &expected_mines)| {
(super_cell.clone(), self.total * expected_mines)
})
}
pub fn from_data(
total: f64,
tally: impl Iterator<Item = CollapseResult<'arena, 'cell, T>>,
) -> Self {
Self {
total,
tally: tally.collect(),
}
}
}
fn enumerate_front<'arena, 'cell: 'arena, T: Cell + ?Sized>(
front: PermutedRuleset<'arena, 'cell, T>,
) -> Result<FrontTally<'arena, 'cell, T>, Error> {
let mut tally = FrontTally::new(empty());
tally.tally(front)?;
Ok(tally)
}
fn cell_probabilities<'arena, 'cell: 'arena, T: Cell + ?Sized>(
tallies: Vec<FrontTally<'arena, 'cell, T>>,
mine_prevalence: Either<BoardInfo, f64>,
all_cells: Vec<SuperCell<'arena, 'cell, T>>,
) -> Result<impl Iterator<Item = CollapseResult<'arena, 'cell, T>>, Error> {
struct Iter<
'arena,
'cell: 'arena,
T: Cell + ?Sized + 'cell,
I: Iterator<Item = CollapseResult<'arena, 'cell, T>>,
J: Iterator<Item = CollapseResult<'arena, 'cell, T>>,
> {
inner: Either<I, J>,
}
impl<
'arena,
'cell: 'arena,
T: Cell + ?Sized + 'cell,
I: Iterator<Item = CollapseResult<'arena, 'cell, T>>,
J: Iterator<Item = CollapseResult<'arena, 'cell, T>>,
> Iterator for Iter<'arena, 'cell, T, I, J>
{
type Item = CollapseResult<'arena, 'cell, T>;
fn next(&mut self) -> Option<Self::Item> {
match &mut self.inner {
Either::Left(iter) => iter.next(),
Either::Right(iter) => iter.next(),
}
}
}
Ok(
weight_subtallies(tallies, mine_prevalence, all_cells)?.flat_map(|tally| {
Iter {
inner: match tally {
Either::Left(mut tally) => {
Either::Left(tally.collapse().into_iter())
},
Either::Right(tally) => Either::Right(tally.collapse()),
},
}
}),
)
}
fn weight_subtallies<'arena, 'cell: 'arena, T: Cell + ?Sized>(
mut tallies: Vec<FrontTally<'arena, 'cell, T>>,
mine_prevalence: Either<BoardInfo, f64>,
all_cells: Vec<SuperCell<'arena, 'cell, T>>,
) -> Result<
Box<
dyn Iterator<Item = Either<FrontTally<'arena, 'cell, T>, FixedProbTally>>
+ 'arena,
>,
Error,
> {
Ok(match mine_prevalence {
Either::Left(board_info) => {
let num_uncharted_cells = check_count_consistency(
&tallies.iter().collect_vec(),
board_info,
all_cells,
)?;
let (static_tallies, mut dyn_tallies) = {
let mut static_tallies = vec![];
let mut dyn_tallies = vec![];
for tally in tallies {
if tally.is_static() {
static_tallies.push(tally);
} else {
dyn_tallies.push(tally);
}
}
(static_tallies, dyn_tallies)
};
let num_static_mines = static_tallies
.iter()
.map(|tally| tally.max_mines())
.sum::<usize>();
let at_large_mines = board_info.total_mines - num_static_mines;
let tally_uncharted =
combine_fronts(&mut dyn_tallies, num_uncharted_cells, at_large_mines);
Box::new(
static_tallies
.into_iter()
.chain(dyn_tallies.into_iter())
.chain(once(tally_uncharted))
.map(Either::Left),
)
},
Either::Right(mine_probability) => {
let tally_uncharted = weight_nondiscrete(
tallies.iter_mut().filter(|tally| tally.is_static()),
mine_probability,
)?;
Box::new(
tallies
.into_iter()
.map(Either::Left)
.chain(once(Either::Right(tally_uncharted))),
)
},
})
}
fn weight_nondiscrete<'a, 'arena: 'a, 'cell: 'arena, T: Cell + ?Sized + 'cell>(
dyn_tallies: impl IntoIterator<Item = &'a mut FrontTally<'arena, 'cell, T>>,
mine_probability: f64,
) -> Result<FixedProbTally, Error> {
for tally in dyn_tallies.into_iter() {
let min_mines = tally.min_mines() as f64;
tally.scale_weights(|num_mines| {
nondiscrete_relative_likelihood(
mine_probability,
num_mines as f64,
min_mines,
)
})?;
}
Ok(FixedProbTally::new(mine_probability))
}
fn check_count_consistency<T: Cell + ?Sized>(
tallies: &[&FrontTally<'_, '_, T>],
board_info: BoardInfo,
all_cells: Vec<&FrozenSet<&T>>,
) -> Result<usize, Error> {
let (min_possible_mines, max_possible_mines) =
possible_mine_limits(tallies.into_iter().copied());
let num_uncharted_cells = board_info.total_cells
- all_cells
.into_iter()
.map(|super_cell| super_cell.len())
.sum::<usize>();
if min_possible_mines > board_info.total_mines {
Err("Minimum possible number of mines exceeds supplied mine count")
} else if board_info.total_mines > max_possible_mines + num_uncharted_cells {
Err("Supplied mine count exceeds maximum possible number of mines")
} else {
Ok(num_uncharted_cells)
}
}
fn combine_fronts<'arena, 'cell: 'arena, T: Cell + ?Sized>(
tallies: &mut Vec<FrontTally<'arena, 'cell, T>>,
num_uncharted_cells: usize,
at_large_mines: usize,
) -> FrontTally<'arena, 'cell, T> {
struct FrontPerMineTotals {
totals: HashMap<usize, f64>,
}
impl FrontPerMineTotals {
pub fn singleton(num_mines: usize, total: f64) -> Self {
Self {
totals: [(num_mines, total)].into(),
}
}
pub fn total_count(&self) -> f64 {
self.totals.values().sum::<f64>()
}
pub fn multiply(&self, n: f64) -> Self {
Self {
totals: self.totals.iter().map(|(&k, &v)| (k, v * n)).collect(),
}
}
pub fn sum<'a>(front_totals: impl Iterator<Item = &'a Self>) -> Self {
Self {
totals: map_reduce(
front_totals,
|front_totals| front_totals.totals.iter().map(|(&k, &v)| (k, v)),
|vals| vals.into_iter().sum::<f64>(),
),
}
}
}
struct AllFrontsPerMineTotals {
front_totals: Vec<FrontPerMineTotals>,
}
impl AllFrontsPerMineTotals {
pub fn total_count(&self) -> f64 {
self.front_totals
.get(0)
.map_or(1.0, |front_total| front_total.total_count())
}
pub fn null() -> Self {
Self {
front_totals: Vec::new(),
}
}
pub fn singleton(num_mines: usize, total: f64) -> Self {
Self {
front_totals: vec![FrontPerMineTotals::singleton(num_mines, total)],
}
}
pub fn join_with(&self, other: &Self) -> Self {
Self {
front_totals: self
.front_totals
.iter()
.map(|f| f.multiply(other.total_count()))
.chain(
other
.front_totals
.iter()
.map(|f| f.multiply(self.total_count())),
)
.collect(),
}
}
pub fn sum(front_sets: &[Self]) -> Self {
if front_sets.is_empty() {
return Self::null();
}
Self {
front_totals: {
let mut results = vec![];
for i in 0..front_sets
.iter()
.map(|set| set.front_totals.len())
.min()
.expect("Already validated that there is at least one element")
{
let mut el = vec![];
for front in front_sets {
el.push(&front.front_totals[i]);
}
results.push(FrontPerMineTotals::sum(el.into_iter()));
}
results
},
}
}
}
struct CombinedFront {
totals: HashMap<usize, AllFrontsPerMineTotals>,
}
impl CombinedFront {
pub fn min_max_mines(&self) -> (usize, usize) {
self.totals
.keys()
.fold((usize::MAX, 0), |(lowest, highest), &next| {
(min(lowest, next), max(highest, next))
})
}
pub fn null() -> Self {
Self {
totals: [(0, AllFrontsPerMineTotals::null())].into(),
}
}
pub fn from_counts_per_num_mines(
mines_with_count: impl Iterator<Item = (usize, f64)>,
) -> Self {
Self {
totals: mines_with_count
.map(|(num_mines, total)| {
(
num_mines,
AllFrontsPerMineTotals::singleton(num_mines, total),
)
})
.collect(),
}
}
pub fn from_tally<T: Cell + ?Sized>(tally: &FrontTally<'_, '_, T>) -> Self {
Self::from_counts_per_num_mines(
tally
.iter()
.map(|(num_mines, subtally)| (num_mines, subtally.total)),
)
}
pub fn for_other(
min_mines: usize,
max_mines: usize,
relative_likelihood: impl Fn(usize) -> f64,
) -> Self {
Self::from_counts_per_num_mines(
(min_mines..=max_mines).map(|n| (n, relative_likelihood(n))),
)
}
pub fn join_with(
&self,
other: &Self,
min_remaining_mines: usize,
max_remaining_mines: usize,
at_large_mines: usize,
) -> Self {
let cross_entry = |((a_num_mines, a_fronts), (b_num_mines, b_fronts)): (
(&usize, &AllFrontsPerMineTotals),
(&usize, &AllFrontsPerMineTotals),
)| {
let combined_mines = a_num_mines + b_num_mines;
let min_mines_at_end = combined_mines + min_remaining_mines;
let max_mines_at_end = combined_mines + max_remaining_mines;
if min_mines_at_end > at_large_mines
|| max_mines_at_end < at_large_mines
{
None
} else {
Some((combined_mines, a_fronts.join_with(b_fronts)))
}
};
let cross_entries = self
.totals
.iter()
.cartesian_product(other.totals.iter())
.filter_map(cross_entry);
let new_totals = map_reduce(cross_entries, once, |vals| {
AllFrontsPerMineTotals::sum(&vals)
});
Self {
totals: new_totals,
}
}
pub fn collapse(self) -> impl Iterator<Item = HashMap<usize, f64>> {
assert_eq!(self.totals.len(), 1);
let item = peek(self.totals.into_iter());
item.1.front_totals.into_iter().map(|e| e.totals)
}
}
let (min_tallied_mines, max_tallied_mines) = possible_mine_limits(tallies.iter());
let min_other_mines = at_large_mines.saturating_sub(max_tallied_mines);
let max_other_mines = min(at_large_mines - min_tallied_mines, num_uncharted_cells);
let relative_likelihood = |num_free_mines: usize| {
discrete_relative_likelihood(
num_uncharted_cells,
num_free_mines,
max_other_mines,
)
.expect(concat!(
"num_free_mines and max_other_mines should always",
" be <= num_uncharted_cells",
))
};
let all_fronts = tallies
.iter()
.map(|t| CombinedFront::from_tally(&t))
.chain(once(CombinedFront::for_other(
min_other_mines,
max_other_mines,
relative_likelihood,
)))
.collect_vec();
let (mut min_remaining_mines, mut max_remaining_mines) = {
let (mins, maxs): (Vec<_>, Vec<_>) =
all_fronts.iter().map(|f| f.min_max_mines()).unzip();
(
mins.into_iter().sum::<usize>(),
maxs.into_iter().sum::<usize>(),
)
};
let mut combined = CombinedFront::null();
for f in all_fronts {
let (front_min, front_max) = f.min_max_mines();
min_remaining_mines -= front_min;
max_remaining_mines -= front_max;
combined = combined.join_with(
&f,
min_remaining_mines,
max_remaining_mines,
at_large_mines,
);
}
let front_totals = combined.collapse().collect_vec();
let (uncharted_total, front_totals) = front_totals
.split_last()
.expect("Should always get at least one total out of collapse");
for (tally, front_total) in tallies.iter_mut().zip(front_totals) {
tally.update_weights(front_total);
}
FrontTally::for_other(num_uncharted_cells, uncharted_total)
}
fn possible_mine_limits<'a, T: Cell + ?Sized + 'a>(
tallies: impl Iterator<Item = &'a FrontTally<'a, 'a, T>>,
) -> (usize, usize) {
tallies
.map(|tally| (tally.min_mines(), tally.max_mines()))
.reduce(|(min, max), (tmin, tmax)| (min + tmin, max + tmax))
.unwrap_or((0, 0))
}
fn nondiscrete_relative_likelihood(p: f64, k: f64, k0: f64) -> Result<f64, Error> {
if (0.0..=1.0).contains(&p) {
Ok((p / (1.0 - p)).powf(k - k0))
} else {
Err("p must be in [0, 1]")
}
}
fn discrete_relative_likelihood(n: usize, k: usize, k0: usize) -> Result<f64, Error> {
if k > n || k0 > n {
Err("k, k0 must be in [0, n]")
} else {
Ok(fact_div(k0, k) * fact_div(n - k0, n - k))
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct UnchartedCell {
pub len: usize,
}
impl UnchartedCell {
pub fn new(len: usize) -> Self {
Self {
len,
}
}
}
struct FixedProbTally {
p: f64,
}
impl FixedProbTally {
pub fn new(p: f64) -> Self {
Self {
p,
}
}
pub fn collapse<'arena, 'cell: 'arena, T: Cell + ?Sized + 'cell>(
&self,
) -> impl Iterator<Item = CollapseResult<'arena, 'cell, T>> {
once((Either::Right(UnchartedCell::new(1)), self.p))
}
}
type ExpandResult<'cell, T> = (&'cell T, f64);
fn expand_cells<'arena, 'cell: 'arena, T: Cell + ?Sized>(
cell_probs: impl Iterator<Item = CollapseResult<'arena, 'cell, T>> + 'arena,
other_tag: &'cell T,
) -> impl Iterator<Item = (&'cell T, f64)> + 'arena {
struct Iter<
'arena,
'cell: 'arena,
T: Hash + Eq + ?Sized,
I: Iterator<Item = CollapseResult<'arena, 'cell, T>>,
> {
cell_probs: I,
inner: Either<
<HashSet<&'cell T> as IntoIterator>::IntoIter,
<Option<(&'cell T, f64)> as IntoIterator>::IntoIter,
>,
p: f64,
other_tag: &'cell T,
}
impl<
'arena,
'cell,
T: Hash + Eq + ?Sized,
I: Iterator<Item = CollapseResult<'arena, 'cell, T>>,
> Iterator for Iter<'arena, 'cell, T, I>
{
type Item = ExpandResult<'cell, T>;
fn next(&mut self) -> Option<Self::Item> {
match self.inner {
Either::Left(ref mut inner) => inner.next().map(|t| (t, self.p)),
Either::Right(ref mut inner) => inner.next(),
}
.or_else(|| {
let (super_cell, p) = self.cell_probs.next()?;
self.p = p;
self.inner = match super_cell {
Either::Left(cells) => Either::Left((*cells).clone().into_iter()),
Either::Right(uncharted) => {
Either::Right(
if uncharted.len != 0 {
Some((self.other_tag, self.p / (uncharted.len as f64)))
} else {
None
}
.into_iter(),
)
},
};
self.next()
})
}
}
Iter {
cell_probs,
inner: Either::Right(None.into_iter()),
p: 0.0,
other_tag,
}
}