use std::{fmt::Display, iter::FusedIterator};
use itertools::Itertools;
use crate::squarecolor::{ALL_SQUARE_COLORS, SquareColor};
type SetBits = u16;
pub const MAX_SET_SIZE: usize = SetBits::BITS as usize;
fn assert_line_in_bounds(line: usize) {
assert!(
line < MAX_SET_SIZE,
"line index {line} is out of range for LineSet; expected 0..{MAX_SET_SIZE}"
);
}
fn assert_coord_in_bounds(coord: Coord) {
assert!(
coord.0 < MAX_SET_SIZE && coord.1 < MAX_SET_SIZE,
"coordinate {coord:?} is out of range for CoordSet; each component must be in 0..{MAX_SET_SIZE}"
);
}
pub type Coord = (usize, usize);
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct SquareColorSet(SetBits);
impl Display for SquareColorSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:?}",
ALL_SQUARE_COLORS
.iter()
.filter(|&c| self.contains(c))
.collect::<Vec<_>>()
)
}
}
impl FromIterator<SquareColor> for SquareColorSet {
fn from_iter<T: IntoIterator<Item = SquareColor>>(iter: T) -> Self {
let mut scs = 0;
for sc in iter {
scs |= 1 << (sc as usize)
}
SquareColorSet(scs)
}
}
impl SquareColorSet {
pub fn len(&self) -> usize {
self.0.count_ones() as usize
}
pub fn is_empty(&self) -> bool {
self.0 == 0
}
pub fn contains(&self, color: &SquareColor) -> bool {
((self.0 >> (*color as usize)) & 1) == 1
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct LineSet(SetBits);
impl Display for LineSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:?}",
(0..MAX_SET_SIZE)
.filter(|c| self.contains(c))
.collect::<Vec<_>>()
)
}
}
impl FromIterator<usize> for LineSet {
fn from_iter<T: IntoIterator<Item = usize>>(iter: T) -> Self {
let mut bits = 0;
for line in iter {
assert_line_in_bounds(line);
bits |= 1 << line;
}
LineSet(bits)
}
}
impl LineSet {
pub fn len(&self) -> usize {
self.0.count_ones() as usize
}
pub fn is_empty(&self) -> bool {
self.0 == 0
}
pub fn contains(&self, line: &usize) -> bool {
assert_line_in_bounds(*line);
((self.0 >> *line) & 1) == 1
}
pub fn iter(&self) -> LineSetIter<'_> {
LineSetIter {
line_set: self,
idx: 0,
}
}
}
pub struct LineSetIter<'a> {
line_set: &'a LineSet,
idx: usize,
}
impl Iterator for LineSetIter<'_> {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
while self.idx < MAX_SET_SIZE {
if self.line_set.contains(&self.idx) {
self.idx += 1;
return Some(self.idx - 1);
}
self.idx += 1;
}
None
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, Some(MAX_SET_SIZE - self.idx))
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct CoordSet([SetBits; MAX_SET_SIZE]);
impl Display for CoordSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:?}",
(0..MAX_SET_SIZE)
.cartesian_product(0..MAX_SET_SIZE)
.filter(|c| self.contains(c))
.collect::<Vec<_>>()
)
}
}
impl<'a> FromIterator<&'a Coord> for CoordSet {
fn from_iter<T: IntoIterator<Item = &'a Coord>>(iter: T) -> Self {
let mut coord_set = CoordSet::default();
for coord in iter {
coord_set.add(*coord);
}
coord_set
}
}
impl FromIterator<Coord> for CoordSet {
fn from_iter<T: IntoIterator<Item = Coord>>(iter: T) -> Self {
let mut coord_set = CoordSet::default();
for coord in iter {
coord_set.add(coord);
}
coord_set
}
}
impl CoordSet {
pub fn len(&self) -> usize {
self.0.map(SetBits::count_ones).iter().sum::<u32>() as usize
}
pub fn is_empty(&self) -> bool {
self.0.iter().all(|b| *b == 0)
}
pub fn contains(&self, coord: &Coord) -> bool {
assert_coord_in_bounds(*coord);
((self.0[coord.0] >> (coord.1)) & 1) == 1
}
pub fn add(&mut self, c: Coord) {
assert_coord_in_bounds(c);
self.0[c.0] |= 1 << c.1;
}
pub fn union<'a>(&'a self, other: &'a CoordSet) -> CoordSet {
let mut new_set = CoordSet::default();
for a in 0..MAX_SET_SIZE {
new_set.0[a] = self.0[a] | other.0[a];
}
new_set
}
pub fn intersection<'a>(&'a self, other: &'a CoordSet) -> CoordSet {
let mut new_set = CoordSet::default();
for a in 0..MAX_SET_SIZE {
new_set.0[a] = self.0[a] & other.0[a];
}
new_set
}
pub fn iter(&self) -> CoordSetIter<'_> {
CoordSetIter::new(self)
}
}
impl Extend<Coord> for CoordSet {
fn extend<T: IntoIterator<Item = Coord>>(&mut self, iter: T) {
for elem in iter {
self.add(elem);
}
}
}
pub struct CoordSetIter<'a> {
coord_set: &'a CoordSet,
row: usize,
remaining: SetBits,
}
impl<'a> CoordSetIter<'a> {
fn new(coord_set: &'a CoordSet) -> Self {
Self {
coord_set,
row: 0,
remaining: coord_set.0[0],
}
}
}
impl<'a> IntoIterator for &'a CoordSet {
type Item = Coord;
type IntoIter = CoordSetIter<'a>;
fn into_iter(self) -> Self::IntoIter {
CoordSetIter::new(self)
}
}
impl Iterator for CoordSetIter<'_> {
type Item = Coord;
fn next(&mut self) -> Option<Self::Item> {
if self.row == MAX_SET_SIZE {
return None;
}
loop {
if self.remaining != 0 {
let column = self.remaining.trailing_zeros() as usize;
self.remaining &= self.remaining - 1;
return Some((self.row, column));
}
self.row += 1;
if self.row == MAX_SET_SIZE {
return None;
}
self.remaining = self.coord_set.0[self.row];
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
if self.row == MAX_SET_SIZE {
return (0, Some(0));
}
let remaining = self.remaining.count_ones() as usize
+ self.coord_set.0[(self.row + 1)..]
.iter()
.map(|bits| bits.count_ones() as usize)
.sum::<usize>();
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for CoordSetIter<'_> {}
impl FusedIterator for CoordSetIter<'_> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn square_color_set() {
let sqs = SquareColorSet::from_iter([
SquareColor::Black,
SquareColor::White,
SquareColor::Black,
SquareColor::Blue,
]);
assert_eq!(sqs.len(), 3);
assert!(!sqs.is_empty());
assert!(sqs.contains(&SquareColor::Black));
assert!(!sqs.contains(&SquareColor::Red));
assert_eq!(format!("{sqs}"), "[Black, Blue, White]");
}
#[test]
fn line_set() {
let ls = LineSet::from_iter([0, 2, 0, 5]);
assert_eq!(ls.len(), 3);
assert!(!ls.is_empty());
assert!(ls.contains(&0));
assert!(!ls.contains(&1));
assert_eq!(ls.iter().collect::<Vec<_>>(), vec![0, 2, 5]);
assert_eq!(format!("{ls}"), "[0, 2, 5]");
}
#[test]
#[should_panic(expected = "is out of range for LineSet")]
fn line_set_rejects_out_of_range_values() {
let _ = LineSet::from_iter([MAX_SET_SIZE]);
}
#[test]
#[should_panic(expected = "is out of range for LineSet")]
fn line_set_rejects_out_of_range_queries() {
LineSet::default().contains(&MAX_SET_SIZE);
}
#[test]
fn coord_set() {
let mut cs = CoordSet::from_iter([(0, 0), (1, 1), (0, 0), (2, 4)]);
assert_eq!(cs.len(), 3);
assert!(!cs.is_empty());
assert!(cs.contains(&(0, 0)));
assert!(!cs.contains(&(5, 5)));
let mut iter = cs.iter();
assert_eq!(iter.len(), 3);
assert_eq!(iter.next(), Some((0, 0)));
assert_eq!(iter.len(), 2);
assert_eq!(iter.collect::<Vec<_>>(), vec![(1, 1), (2, 4)]);
assert_eq!(format!("{cs}"), "[(0, 0), (1, 1), (2, 4)]");
cs.extend([(5, 5)]);
assert!(cs.contains(&(5, 5)));
let other = CoordSet::from_iter([(2, 4), (6, 6)]);
assert_eq!(
cs.union(&other),
CoordSet::from_iter([(0, 0), (1, 1), (2, 4), (5, 5), (6, 6)])
);
let empty = CoordSet::default();
let mut empty_iter = empty.iter();
assert_eq!(empty_iter.next(), None);
assert_eq!(empty_iter.next(), None);
assert_eq!(empty_iter.len(), 0);
}
#[test]
#[should_panic(expected = "is out of range for CoordSet")]
fn coord_set_rejects_out_of_range_values() {
let _ = CoordSet::from_iter([(MAX_SET_SIZE, 0)]);
}
#[test]
#[should_panic(expected = "is out of range for CoordSet")]
fn coord_set_rejects_out_of_range_queries() {
CoordSet::default().contains(&(0, MAX_SET_SIZE));
}
#[test]
#[should_panic(expected = "is out of range for CoordSet")]
fn coord_set_rejects_out_of_range_additions() {
CoordSet::default().add((MAX_SET_SIZE, 0));
}
}