Add 'Bitboard::iter_powerset'

This commit is contained in:
Bruno BELANYI 2022-07-21 20:25:29 +02:00
parent a3a9f64213
commit 8d03242e83
2 changed files with 131 additions and 1 deletions

View file

@ -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<Item = Self> {
BitboardPowerSetIterator::new(self)
}
}
// Ensure zero-cost (at least size-wise) wrapping.
@ -196,8 +207,10 @@ impl std::ops::Sub<Square> 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,75 @@ 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<_>>(),
vec![Bitboard::EMPTY]
)
}
#[test]
fn iter_power_set_one_square() {
for square in Square::iter() {
assert_eq!(
square
.into_bitboard()
.iter_power_set()
.collect::<HashSet<_>>(),
[Bitboard::EMPTY, square.into_bitboard()]
.into_iter()
.collect::<HashSet<_>>()
)
}
}
#[test]
fn iter_power_set_two_squares() {
assert_eq!(
(Square::A1 | Square::H8)
.iter_power_set()
.collect::<HashSet<_>>(),
[
Bitboard::EMPTY,
Square::A1.into_bitboard(),
Square::H8.into_bitboard(),
Square::A1 | Square::H8
]
.into_iter()
.collect::<HashSet<_>>()
)
}
#[test]
fn iter_power_set_six_squares_exhaustive() {
let mask = (0..6)
.map(Square::from_index)
.fold(Bitboard::EMPTY, |lhs, rhs| lhs | rhs);
assert_eq!(
mask.iter_power_set().collect::<HashSet<_>>(),
(0..(1 << 6)).map(Bitboard).collect::<HashSet<_>>()
)
}
#[test]
fn iter_power_set_eight_squares_length() {
assert_eq!(
File::A
.into_bitboard()
.iter_power_set()
.collect::<HashSet<_>>()
.len(),
1 << 8
);
assert_eq!(
Rank::First
.into_bitboard()
.iter_power_set()
.collect::<HashSet<_>>()
.len(),
1 << 8
);
}
}

View file

@ -0,0 +1,46 @@
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 starting board.
board: Bitboard,
/// The next subset.
subset: Bitboard,
/// Whether or not iteration is done.
done: bool,
}
impl BitboardPowerSetIterator {
pub fn new(board: Bitboard) -> Self {
Self {
board,
subset: Bitboard::EMPTY,
done: false,
}
}
}
impl Iterator for BitboardPowerSetIterator {
type Item = Bitboard;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
let res = self.subset;
self.subset = Bitboard(self.subset.0.wrapping_sub(self.board.0)) & self.board;
self.done = self.subset.is_empty();
Some(res)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let size = 1 << self.board.count();
(size, Some(size))
}
}
impl ExactSizeIterator for BitboardPowerSetIterator {}
impl std::iter::FusedIterator for BitboardPowerSetIterator {}