use rustc_hash::FxHashMap;
use crate::cyclotomic::IsRing;
static NEIGHBOR_OFFSETS: [&[(i64, i64)]; 9] = [
&[
(-2, -1),
(-1, -2),
(-1, -1),
(-1, 0),
(0, -1),
(0, 0),
(0, 1),
(1, 0),
],
&[
(-2, 0),
(-1, -1),
(-1, 0),
(-1, 1),
(0, -1),
(0, 0),
(0, 1),
(1, 0),
],
&[
(-2, 1),
(-1, 0),
(-1, 1),
(-1, 2),
(0, -1),
(0, 0),
(0, 1),
(1, 0),
],
&[
(-1, -1),
(-1, 0),
(0, -2),
(0, -1),
(0, 0),
(0, 1),
(1, -1),
(1, 0),
],
&[(-1, 0), (0, -1), (0, 0), (0, 1), (1, 0)],
&[
(-1, 0),
(-1, 1),
(0, -1),
(0, 0),
(0, 1),
(0, 2),
(1, 0),
(1, 1),
],
&[
(-1, 0),
(0, -1),
(0, 0),
(0, 1),
(1, -2),
(1, -1),
(1, 0),
(2, -1),
],
&[
(-1, 0),
(0, -1),
(0, 0),
(0, 1),
(1, -1),
(1, 0),
(1, 1),
(2, 0),
],
&[
(-1, 0),
(0, -1),
(0, 0),
(0, 1),
(1, 0),
(1, 1),
(1, 2),
(2, 1),
],
];
#[derive(Debug, Clone)]
pub struct UnitSquareGrid {
cells: FxHashMap<(i64, i64), Vec<usize>>,
}
impl Default for UnitSquareGrid {
fn default() -> Self {
Self::new()
}
}
impl UnitSquareGrid {
pub fn cell_of<T: IsRing>(zz: T) -> (i64, i64) {
zz.cell_floor()
}
#[inline]
pub fn edge_neighborhood_of<T: IsRing>(p1: T, p2: T) -> impl Iterator<Item = (i64, i64)> {
let (cx, cy) = Self::cell_of(p1);
let (cx2, cy2) = Self::cell_of(p2);
let dx = cx2 - cx;
let dy = cy2 - cy;
debug_assert!(
dx.abs() <= 1 && dy.abs() <= 1,
"edge_neighborhood_of: endpoints not unit-distance ({dx}, {dy})"
);
let idx = ((dx + 1) * 3 + (dy + 1)) as usize;
NEIGHBOR_OFFSETS[idx]
.iter()
.map(move |&(ox, oy)| (cx + ox, cy + oy))
}
pub fn new() -> Self {
Self {
cells: FxHashMap::default(),
}
}
pub fn len(&self) -> usize {
self.cells.values().map(|v| v.len()).sum()
}
pub fn is_empty(&self) -> bool {
self.cells.values().all(|v| v.is_empty())
}
pub fn add(&mut self, cell: (i64, i64), value: usize) {
self.cells.entry(cell).or_default().push(value);
}
pub fn remove(&mut self, cell: (i64, i64), value: usize) {
if let Some(vals) = self.cells.get_mut(&cell) {
if let Some(pos) = vals.iter().position(|&v| v == value) {
vals.swap_remove(pos);
}
if vals.is_empty() {
self.cells.remove(&cell);
}
}
}
pub fn get(&self, cell: (i64, i64)) -> &[usize] {
self.cells.get(&cell).map(Vec::as_slice).unwrap_or(&[])
}
pub fn get_cells(&self, cells: &[(i64, i64)]) -> Vec<usize> {
let total_entries: usize = cells
.iter()
.map(|&cell| self.cells.get(&cell).map(Vec::len).unwrap_or(0))
.sum();
let mut result = Vec::with_capacity(total_entries);
for &cell in cells {
if let Some(vals) = self.cells.get(&cell) {
result.extend(vals);
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cyclotomic::{SymNum, Units, ZZ12};
use num_traits::{One, Zero};
#[test]
fn test_cell_of() {
assert_eq!(UnitSquareGrid::cell_of(ZZ12::zero()), (0, 0));
assert_eq!(UnitSquareGrid::cell_of(<ZZ12 as Units>::unit(0)), (1, 0));
assert_eq!(UnitSquareGrid::cell_of(<ZZ12 as Units>::unit(1)), (0, 0));
assert_eq!(UnitSquareGrid::cell_of(<ZZ12 as Units>::unit(2)), (0, 0));
assert_eq!(UnitSquareGrid::cell_of(<ZZ12 as Units>::unit(3)), (0, 1));
assert_eq!(UnitSquareGrid::cell_of(<ZZ12 as Units>::unit(4)), (-1, 0));
assert_eq!(UnitSquareGrid::cell_of(<ZZ12 as Units>::unit(5)), (-1, 0));
assert_eq!(UnitSquareGrid::cell_of(<ZZ12 as Units>::unit(6)), (-1, 0));
assert_eq!(UnitSquareGrid::cell_of(<ZZ12 as Units>::unit(7)), (-1, -1));
assert_eq!(UnitSquareGrid::cell_of(<ZZ12 as Units>::unit(8)), (-1, -1));
assert_eq!(UnitSquareGrid::cell_of(<ZZ12 as Units>::unit(9)), (0, -1));
assert_eq!(UnitSquareGrid::cell_of(<ZZ12 as Units>::unit(10)), (0, -1));
assert_eq!(UnitSquareGrid::cell_of(<ZZ12 as Units>::unit(11)), (0, -1));
}
#[test]
fn test_edge_neighborhood_of() {
let cells_of = |p1: ZZ12, p2: ZZ12| -> Vec<(i64, i64)> {
UnitSquareGrid::edge_neighborhood_of(p1, p2).collect()
};
let tmp1 =
(ZZ12::one().scale(2) - <ZZ12 as Units>::unit(2) - <ZZ12 as Units>::unit(-1).scale(2))
* <ZZ12 as Units>::unit(1);
let tmp2 =
(ZZ12::one().scale(3) - <ZZ12 as Units>::unit(2) - <ZZ12 as Units>::unit(-1).scale(3))
* <ZZ12 as Units>::unit(2);
let p1 = (tmp1 - tmp2) * <ZZ12 as Units>::unit(-2);
let p2 = p1 + <ZZ12 as Units>::unit(2);
assert_eq!(
cells_of(p1, p2),
vec![
(-2, -1),
(-1, -2),
(-1, -1),
(-1, 0),
(0, -1),
(0, 0),
(0, 1),
(1, 0),
]
);
assert_eq!(
cells_of(ZZ12::zero(), <ZZ12 as Units>::unit(0)),
vec![
(-1, 0),
(0, -1),
(0, 0),
(0, 1),
(1, -1),
(1, 0),
(1, 1),
(2, 0),
]
);
assert_eq!(
cells_of(ZZ12::zero(), <ZZ12 as Units>::unit(3)),
vec![
(-1, 0),
(-1, 1),
(0, -1),
(0, 0),
(0, 1),
(0, 2),
(1, 0),
(1, 1),
]
);
assert_eq!(
cells_of(ZZ12::zero(), <ZZ12 as Units>::unit(1)),
vec![(-1, 0), (0, -1), (0, 0), (0, 1), (1, 0)]
);
}
#[test]
fn test_edge_neighborhood_of_matches_reference() {
let mut diffs = 0usize;
for ax in -3..=3i64 {
for ay in -3..=3i64 {
for bx in -3..=3i64 {
for by in -3..=3i64 {
for dir in 0..12i8 {
let p1: ZZ12 = ZZ12::from((ax, ay))
+ <ZZ12 as Units>::unit(2).scale(bx)
+ <ZZ12 as Units>::unit(2).scale(by) * <ZZ12 as Units>::unit(3);
let p2 = p1 + <ZZ12 as Units>::unit(dir);
let mut from_table: Vec<(i64, i64)> =
UnitSquareGrid::edge_neighborhood_of(p1, p2).collect();
from_table.sort_unstable();
from_table.dedup();
let cross = |p: ZZ12| -> [(i64, i64); 5] {
let (x, y) = UnitSquareGrid::cell_of(p);
[(x - 1, y), (x, y - 1), (x, y), (x, y + 1), (x + 1, y)]
};
let mut reference: Vec<(i64, i64)> = Vec::with_capacity(10);
reference.extend_from_slice(&cross(p1));
reference.extend_from_slice(&cross(p2));
reference.sort_unstable();
reference.dedup();
if from_table != reference {
if diffs < 5 {
let c1 = UnitSquareGrid::cell_of(p1);
let c2 = UnitSquareGrid::cell_of(p2);
eprintln!(
"DIFF at (ax,ay,bx,by,dir)=({ax},{ay},{bx},{by},{dir}): \
cell(p1)={c1:?}, cell(p2)={c2:?}"
);
eprintln!(" from_table: {from_table:?}");
eprintln!(" reference: {reference:?}");
}
diffs += 1;
}
}
}
}
}
}
assert_eq!(
diffs, 0,
"{diffs} (p1, dir) cases disagree with the reference"
);
}
#[test]
fn test_get_cells() {
let mut grid = UnitSquareGrid::new();
grid.add((0, 0), 0);
grid.add((0, 0), 1);
grid.add((1, 0), 2);
grid.add((1, 0), 0);
let empty: &[usize] = &[];
assert_eq!(grid.get((0, 0)), &[0, 1]);
assert_eq!(grid.get((1, 0)), &[2, 0]);
assert_eq!(grid.get((2, 0)), empty);
assert_eq!(grid.get_cells(&[(0, 0)]), &[0, 1]);
assert_eq!(grid.get_cells(&[(1, 0)]), &[2, 0]);
assert_eq!(grid.get_cells(&[(2, 0)]), empty);
assert_eq!(grid.get_cells(&[(2, 0), (1, 0), (0, 0)]), &[2, 0, 0, 1]);
}
#[test]
fn test_remove_basic() {
let mut grid = UnitSquareGrid::new();
grid.add((0, 0), 10);
grid.add((0, 0), 20);
grid.add((0, 0), 30);
grid.add((1, 1), 40);
assert_eq!(grid.len(), 4);
grid.remove((0, 0), 20);
assert_eq!(grid.get((0, 0)), &[10, 30]);
assert_eq!(grid.len(), 3);
grid.remove((0, 0), 10);
assert_eq!(grid.get((0, 0)), &[30]);
assert_eq!(grid.len(), 2);
grid.remove((0, 0), 30);
let empty: &[usize] = &[];
assert_eq!(grid.get((0, 0)), empty);
assert!(!grid.cells.contains_key(&(0, 0)));
assert_eq!(grid.len(), 1);
grid.remove((1, 1), 40);
assert!(!grid.cells.contains_key(&(1, 1)));
assert!(grid.is_empty());
}
#[test]
fn test_remove_nonexistent() {
let mut grid = UnitSquareGrid::new();
grid.add((0, 0), 1);
grid.remove((0, 0), 999);
assert_eq!(grid.get((0, 0)), &[1]);
assert_eq!(grid.len(), 1);
grid.remove((5, 5), 1);
assert_eq!(grid.get((0, 0)), &[1]);
}
#[test]
fn test_remove_preserves_other_cells() {
let mut grid = UnitSquareGrid::new();
grid.add((0, 0), 1);
grid.add((0, 0), 2);
grid.add((1, 0), 3);
grid.add((1, 1), 4);
grid.remove((0, 0), 1);
assert_eq!(grid.get((0, 0)), &[2]);
assert_eq!(grid.get((1, 0)), &[3]);
assert_eq!(grid.get((1, 1)), &[4]);
}
#[test]
fn test_add_remove_roundtrip() {
let mut grid = UnitSquareGrid::new();
let cells = [(0, 0), (1, 0), (0, 1), (-1, 0), (0, -1), (2, 2), (-2, -2)];
for (i, &cell) in cells.iter().enumerate() {
grid.add(cell, i);
}
assert_eq!(grid.len(), 7);
for (i, &cell) in cells.iter().enumerate() {
grid.remove(cell, i);
}
assert!(grid.is_empty());
assert!(grid.cells.is_empty());
}
#[test]
fn test_multiple_values_same_cell() {
let mut grid = UnitSquareGrid::new();
for i in 0..10 {
grid.add((3, 7), i);
}
assert_eq!(grid.len(), 10);
assert_eq!(grid.get((3, 7)).len(), 10);
for i in (0..10).rev() {
grid.remove((3, 7), i);
}
assert!(grid.is_empty());
assert!(!grid.cells.contains_key(&(3, 7)));
}
#[test]
fn test_clone_independent() {
let mut grid = UnitSquareGrid::new();
grid.add((0, 0), 1);
grid.add((1, 1), 2);
let mut clone = grid.clone();
clone.remove((0, 0), 1);
clone.add((2, 2), 3);
let empty: &[usize] = &[];
assert_eq!(grid.get((0, 0)), &[1]);
assert_eq!(grid.get((2, 2)), empty);
assert_eq!(clone.get((0, 0)), empty);
assert_eq!(clone.get((1, 1)), &[2]);
assert_eq!(clone.get((2, 2)), &[3]);
}
}