From fa68be533a5f7ef0d5483224ef87e49289c06600 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Thu, 21 Jul 2022 20:25:29 +0200 Subject: [PATCH] Add 'Bitboard::iter_powerset' --- src/board/bitboard/mod.rs | 90 +++++++++++++++++++++++++++++++++- src/board/bitboard/superset.rs | 51 +++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/board/bitboard/superset.rs diff --git a/src/board/bitboard/mod.rs b/src/board/bitboard/mod.rs index 8b716be..9f86921 100644 --- a/src/board/bitboard/mod.rs +++ b/src/board/bitboard/mod.rs @@ -3,6 +3,8 @@ use crate::utils::static_assert; mod iterator; use iterator::*; +mod superset; +use superset::*; /// Use a 64-bit number to represent a chessboard. Each bit is mapped from to a specific square, so /// that index 0 -> A1, 1 -> A2, ..., 63 -> H8. @@ -63,6 +65,15 @@ impl Bitboard { pub fn is_empty(self) -> bool { self == Self::EMPTY } + + /// Iterate over the power-set of a given [Bitboard], yielding each possible sub-set of + /// [Square] that belong to the [Bitboard]. In other words, generate all set of [Square] that + /// contain all, some, or none of the [Square] that are in the given [Bitboard]. + /// If given an empty [Bitboard], yields the empty [Bitboard] back. + #[inline(always)] + pub fn iter_power_set(self) -> impl Iterator { + BitboardPowerSetIterator::new(self) + } } // Ensure zero-cost (at least size-wise) wrapping. @@ -196,8 +207,10 @@ impl std::ops::Sub for Bitboard { #[cfg(test)] mod test { + use std::collections::HashSet; + use super::*; - use crate::board::square::*; + use crate::board::{square::*, File, Rank}; #[test] fn count() { @@ -280,4 +293,79 @@ mod test { assert_eq!(Bitboard::FILES[0] - Bitboard::RANKS[0], Bitboard(0xff - 1)); assert_eq!(Bitboard::FILES[0] - Square::A1, Bitboard(0xff - 1)); } + + #[test] + fn iter_power_set_empty() { + assert_eq!( + Bitboard::EMPTY.iter_power_set().collect::>(), + vec![Bitboard::EMPTY] + ) + } + + #[test] + fn iter_power_set_one_square() { + for square in Square::iter() { + assert_eq!( + square + .into_bitboard() + .iter_power_set() + .collect::>(), + [Bitboard::EMPTY, square.into_bitboard()] + .into_iter() + .collect::>() + ) + } + } + + #[test] + fn iter_power_set_two_squares() { + assert_eq!( + (Square::A1 | Square::H8) + .iter_power_set() + .collect::>(), + [ + Bitboard::EMPTY, + Square::A1.into_bitboard(), + Square::H8.into_bitboard(), + Square::A1 | Square::H8 + ] + .into_iter() + .collect::>() + ) + } + + #[test] + fn iter_power_set_six_squares_exhaustive() { + let mask = (0..6) + .into_iter() + .map(Square::from_index) + .fold(Bitboard::EMPTY, |lhs, rhs| lhs | rhs); + assert_eq!( + mask.iter_power_set().collect::>(), + (0..(1 << 6)) + .into_iter() + .map(Bitboard) + .collect::>() + ) + } + + #[test] + fn iter_power_set_eight_squares_length() { + assert_eq!( + File::A + .into_bitboard() + .iter_power_set() + .collect::>() + .len(), + 1 << 8 + ); + assert_eq!( + Rank::First + .into_bitboard() + .iter_power_set() + .collect::>() + .len(), + 1 << 8 + ); + } } diff --git a/src/board/bitboard/superset.rs b/src/board/bitboard/superset.rs new file mode 100644 index 0000000..46f1ad2 --- /dev/null +++ b/src/board/bitboard/superset.rs @@ -0,0 +1,51 @@ +use super::Bitboard; + +/// Iterator over a [Bitboard] mask, which yields all potential subsets of the given board. +/// In other words, for each square that belongs to the mask, this will yield all sets that do +/// contain the square, and all sets that do not. +pub struct BitboardPowerSetIterator { + /// The mask. + mask: Bitboard, + /// The "index" of the next blocker set that should be generated. + current: usize, + /// The number of blocker sets that should be generated by [BlockerIterator], i.e: 2^n with n + /// the number of squares belonging to `mask`. + total: usize, +} + +impl BitboardPowerSetIterator { + pub fn new(mask: Bitboard) -> Self { + Self { + mask, + current: 0, + total: 1 << mask.count(), + } + } +} + +impl Iterator for BitboardPowerSetIterator { + type Item = Bitboard; + + fn next(&mut self) -> Option { + if self.current >= self.total { + None + } else { + let blockers = (0..) + .into_iter() + .zip(self.mask.into_iter()) + .filter(|(index, _)| self.current & (1 << index) != 0) + .map(|(_, board)| board) + .fold(Bitboard::EMPTY, |lhs, rhs| lhs | rhs); + self.current += 1; + Some(blockers) + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.total, Some(self.total)) + } +} + +impl ExactSizeIterator for BitboardPowerSetIterator {} + +impl std::iter::FusedIterator for BitboardPowerSetIterator {}