720 lines
19 KiB
Rust
720 lines
19 KiB
Rust
use std::{
|
|
fmt::{Debug, Display, Formatter, Write},
|
|
io::{BufRead, Cursor},
|
|
iter::repeat_n,
|
|
mem::swap,
|
|
ops::{Add, AddAssign, Sub},
|
|
str::FromStr,
|
|
};
|
|
|
|
/// NW, N, NE, W, E, SW, S, SE
|
|
pub const ADJACENT_OFFSETS: [&(i64, i64); 8] = [
|
|
&(-1, -1),
|
|
&(0, -1),
|
|
&(1, -1),
|
|
&(-1, 0),
|
|
&(1, 0),
|
|
&(-1, 1),
|
|
&(0, 1),
|
|
&(1, 1),
|
|
];
|
|
|
|
/// NESW
|
|
pub const CARDINAL_OFFSETS: [&(i64, i64); 4] = [&(0, -1), &(-1, 0), &(1, 0), &(0, 1)];
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
|
pub struct Coord2d {
|
|
pub x: i64,
|
|
pub y: i64,
|
|
}
|
|
|
|
impl Display for Coord2d {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
f.write_fmt(format_args!("({}, {})", self.x, self.y))
|
|
}
|
|
}
|
|
|
|
impl FromStr for Coord2d {
|
|
type Err = Box<dyn std::error::Error>;
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let (l, r) = s.split_once(',').ok_or("Can't split on ,")?;
|
|
Ok(Coord2d {
|
|
x: l.parse()?,
|
|
y: r.parse()?,
|
|
})
|
|
}
|
|
}
|
|
|
|
pub trait AsCoord2d {
|
|
fn to_coord(self) -> Coord2d;
|
|
fn x(&self) -> i64;
|
|
fn y(&self) -> i64;
|
|
|
|
fn manhattan<T: AsCoord2d>(&self, other: &T) -> u64 {
|
|
self.x().abs_diff(other.x()) + self.y().abs_diff(other.y())
|
|
}
|
|
}
|
|
|
|
impl<T: AsCoord2d> Sub<T> for &Coord2d {
|
|
type Output = Coord2d;
|
|
fn sub(self, rhs: T) -> Self::Output {
|
|
Coord2d {
|
|
x: self.x() - rhs.x(),
|
|
y: self.y() - rhs.y(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: AsCoord2d> Add<T> for &Coord2d
|
|
where
|
|
T: Copy,
|
|
{
|
|
type Output = Coord2d;
|
|
fn add(self, rhs: T) -> Self::Output {
|
|
Coord2d {
|
|
x: self.x() + rhs.x(),
|
|
y: self.y() + rhs.y(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: AsCoord2d + ?Sized> Add<&T> for Coord2d {
|
|
type Output = Coord2d;
|
|
fn add(self, rhs: &T) -> Self::Output {
|
|
Coord2d {
|
|
x: self.x() + rhs.x(),
|
|
y: self.y() + rhs.y(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AsCoord2d for Coord2d {
|
|
fn to_coord(self) -> Coord2d {
|
|
self
|
|
}
|
|
fn x(&self) -> i64 {
|
|
self.x
|
|
}
|
|
fn y(&self) -> i64 {
|
|
self.y
|
|
}
|
|
}
|
|
|
|
impl AsCoord2d for &Coord2d {
|
|
fn to_coord(self) -> Coord2d {
|
|
self.to_owned()
|
|
}
|
|
fn x(&self) -> i64 {
|
|
self.x
|
|
}
|
|
fn y(&self) -> i64 {
|
|
self.y
|
|
}
|
|
}
|
|
|
|
impl<T> AsCoord2d for (T, T)
|
|
where
|
|
T: Copy + TryInto<i64>,
|
|
<T as TryInto<i64>>::Error: Debug,
|
|
{
|
|
fn to_coord(self) -> Coord2d {
|
|
Coord2d {
|
|
x: self.0.try_into().unwrap(),
|
|
y: self.1.try_into().unwrap(),
|
|
}
|
|
}
|
|
fn x(&self) -> i64 {
|
|
self.0.try_into().unwrap()
|
|
}
|
|
fn y(&self) -> i64 {
|
|
self.1.try_into().unwrap()
|
|
}
|
|
}
|
|
|
|
impl<T> AsCoord2d for &(T, T)
|
|
where
|
|
T: Copy + TryInto<i64>,
|
|
<T as TryInto<i64>>::Error: Debug,
|
|
{
|
|
fn to_coord(self) -> Coord2d {
|
|
Coord2d {
|
|
x: self.0.try_into().unwrap(),
|
|
y: self.1.try_into().unwrap(),
|
|
}
|
|
}
|
|
fn x(&self) -> i64 {
|
|
self.0.try_into().unwrap()
|
|
}
|
|
fn y(&self) -> i64 {
|
|
self.1.try_into().unwrap()
|
|
}
|
|
}
|
|
|
|
impl From<Coord2d> for (i64, i64) {
|
|
fn from(value: Coord2d) -> Self {
|
|
(value.x, value.y)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct GridRowIter<'a, T> {
|
|
iter: std::slice::Iter<'a, T>,
|
|
}
|
|
|
|
impl<'a, T: Clone + Eq + PartialEq + Debug> GridRowIter<'a, T> {
|
|
fn new(grid: &'a Grid<T>, y: i64) -> Self {
|
|
let iter = grid.data[y as usize * grid.width()..(y as usize + 1) * grid.width()].iter();
|
|
Self { iter }
|
|
}
|
|
}
|
|
|
|
impl<'a, T> Iterator for GridRowIter<'a, T> {
|
|
type Item = &'a T;
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
self.iter.next()
|
|
}
|
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
self.iter.size_hint()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct CoordIter<'a, T> {
|
|
pos: usize,
|
|
grid: &'a Grid<T>,
|
|
}
|
|
|
|
impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for CoordIter<'a, T> {
|
|
type Item = Coord2d;
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
if self.pos < self.grid.data.len() {
|
|
self.pos += 1;
|
|
self.grid.coord(self.pos as i64 - 1)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ItemIter<'a, T> {
|
|
pos: usize,
|
|
grid: &'a Grid<T>,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct Item<'a, T> {
|
|
pub pos: Coord2d,
|
|
pub value: &'a T,
|
|
}
|
|
|
|
impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for ItemIter<'a, T> {
|
|
type Item = Item<'a, T>;
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
if self.pos < self.grid.data.len() {
|
|
self.pos += 1;
|
|
Some(Item {
|
|
pos: self.grid.coord(self.pos as i64 - 1).unwrap(),
|
|
value: &self.grid.data[self.pos - 1],
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct GridColIter<'a, T> {
|
|
grid: &'a Grid<T>,
|
|
stride: usize,
|
|
cur: usize,
|
|
}
|
|
|
|
impl<'a, T: Clone + Eq + PartialEq + Debug> GridColIter<'a, T> {
|
|
fn new(grid: &'a Grid<T>, x: i64) -> Self {
|
|
Self {
|
|
grid,
|
|
stride: grid.width(),
|
|
cur: x as usize,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for GridColIter<'a, T> {
|
|
type Item = &'a T;
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
let cur = self.cur;
|
|
self.cur += self.stride;
|
|
if cur < self.grid.data.len() {
|
|
Some(&self.grid.data[cur])
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
(self.grid.height() - 1, Some(self.grid.height() - 1))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct OffsetsIter<'a, T> {
|
|
grid: &'a Grid<T>,
|
|
origin: Coord2d,
|
|
cur: usize,
|
|
offsets: &'a [&'a (i64, i64)],
|
|
}
|
|
|
|
impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for OffsetsIter<'a, T> {
|
|
type Item = Item<'a, T>;
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
while self.cur < self.offsets.len() {
|
|
let pos = self.origin + self.offsets[self.cur];
|
|
self.cur += 1;
|
|
if let Some(value) = self.grid.get(&pos) {
|
|
return Some(Item { pos, value });
|
|
}
|
|
}
|
|
None
|
|
}
|
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
(0, Some(self.offsets.len() - self.cur))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Eq, PartialEq, Debug)]
|
|
pub struct Grid<T> {
|
|
pub data: Vec<T>,
|
|
width: i64,
|
|
}
|
|
|
|
impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
|
|
/// Returns a new [Grid] with the same shape (width x height) as `self`, filled with `fill`
|
|
pub fn same_shape<NT: Clone + Eq + PartialEq + Debug>(&self, fill: NT) -> Grid<NT> {
|
|
Grid::with_shape(self.width(), self.height(), fill)
|
|
}
|
|
/// Returns a new [Grid] with the given shape (width x height), filled with `fill`
|
|
pub fn with_shape(width: usize, height: usize, fill: T) -> Self {
|
|
Self {
|
|
data: Vec::from_iter(repeat_n(fill, width * height)),
|
|
width: width as i64,
|
|
}
|
|
}
|
|
pub fn width(&self) -> usize {
|
|
self.width as usize
|
|
}
|
|
pub fn height(&self) -> usize {
|
|
self.data.len() / self.width()
|
|
}
|
|
pub fn pos<C: AsCoord2d>(&self, c: &C) -> i64 {
|
|
c.y() * self.width + c.x()
|
|
}
|
|
pub fn coord(&self, pos: i64) -> Option<Coord2d> {
|
|
if pos < 0 || pos >= self.data.len() as i64 {
|
|
None
|
|
} else {
|
|
Some(Coord2d {
|
|
x: pos % self.width,
|
|
y: pos / self.width,
|
|
})
|
|
}
|
|
}
|
|
pub fn coord_iter<'a>(&'a self) -> CoordIter<'a, T> {
|
|
CoordIter { pos: 0, grid: self }
|
|
}
|
|
pub fn item_iter<'a>(&'a self) -> ItemIter<'a, T> {
|
|
ItemIter { pos: 0, grid: self }
|
|
}
|
|
pub fn is_valid<C: AsCoord2d>(&self, c: &C) -> bool {
|
|
if c.x() < 0 || c.x() >= self.width {
|
|
return false;
|
|
}
|
|
if c.y() < 0 || c.y() as usize >= self.height() {
|
|
return false;
|
|
}
|
|
true
|
|
}
|
|
fn valid_pos<C: AsCoord2d>(&self, c: &C) -> Option<usize> {
|
|
if c.x() < 0 || c.x() >= self.width {
|
|
return None;
|
|
}
|
|
if c.y() < 0 || c.y() as usize >= self.height() {
|
|
return None;
|
|
}
|
|
let pos = self.pos(c);
|
|
if pos < 0 || pos as usize >= self.data.len() {
|
|
return None;
|
|
}
|
|
self.pos(c).try_into().ok()
|
|
}
|
|
pub fn get<C: AsCoord2d>(&self, c: &C) -> Option<&T> {
|
|
match self.valid_pos(c) {
|
|
Some(pos) => Some(&self.data[pos]),
|
|
None => None,
|
|
}
|
|
}
|
|
pub fn get_mut<C: AsCoord2d>(&mut self, c: &C) -> Option<&mut T> {
|
|
match self.valid_pos(c) {
|
|
Some(pos) => Some(self.data.get_mut(pos).unwrap()),
|
|
None => None,
|
|
}
|
|
}
|
|
pub fn set<C: AsCoord2d>(&mut self, c: &C, mut val: T) -> Option<T> {
|
|
match self.valid_pos(c) {
|
|
Some(pos) => {
|
|
swap(&mut self.data[pos], &mut val);
|
|
Some(val)
|
|
}
|
|
None => None,
|
|
}
|
|
}
|
|
pub fn increment<'a, A, C: AsCoord2d>(&'a mut self, c: &C, i: A) -> Option<&'a T>
|
|
where
|
|
T: AddAssign<A>,
|
|
{
|
|
match self.valid_pos(c) {
|
|
Some(pos) => {
|
|
self.data[pos] += i;
|
|
Some(&self.data[pos])
|
|
}
|
|
None => None,
|
|
}
|
|
}
|
|
pub fn row(&self, y: i64) -> Option<&[T]> {
|
|
if y < self.height() as i64 && y >= 0 {
|
|
Some(&self.data[self.pos(&(0, y)) as usize..self.pos(&(self.width, y)) as usize])
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn row_iter<'a>(&'a self, y: i64) -> Option<GridRowIter<'a, T>> {
|
|
if (y as usize) < self.height() {
|
|
Some(GridRowIter::new(self, y))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn col(&self, x: i64) -> Option<Vec<&T>> {
|
|
self.col_iter(x).map(|iter| iter.collect())
|
|
}
|
|
|
|
pub fn col_iter<'a>(&'a self, x: i64) -> Option<GridColIter<'a, T>> {
|
|
if (x as usize) < self.width() {
|
|
Some(GridColIter::new(self, x))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn find(&self, needle: &T) -> Option<Coord2d> {
|
|
self.coord(
|
|
self.data
|
|
.iter()
|
|
.enumerate()
|
|
.find_map(|(pos, val)| {
|
|
if val == needle {
|
|
Some(pos as i64)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.unwrap_or(-1),
|
|
)
|
|
}
|
|
/// Get all of the coordinates having value `needle`
|
|
pub fn find_all<'a>(&'a self, needle: &'a T) -> impl DoubleEndedIterator<Item = Coord2d> + 'a {
|
|
self.data
|
|
.iter()
|
|
.enumerate()
|
|
.filter_map(|(pos, val)| (*val == *needle).then_some(self.coord(pos as i64).unwrap()))
|
|
}
|
|
pub fn count(&self, haystack: &T) -> usize {
|
|
self.data.iter().filter(|item| *item == haystack).count()
|
|
}
|
|
|
|
pub fn forward_slice<C: AsCoord2d>(&self, start: &C, len: i64) -> Option<&[T]> {
|
|
let pos = (
|
|
self.valid_pos(start),
|
|
self.valid_pos(&(start.x() + len - 1, start.y())),
|
|
);
|
|
match pos {
|
|
(Some(pos1), Some(pos2)) => Some(&self.data[pos1..pos2 + 1]),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn swap<A: AsCoord2d, B: AsCoord2d>(&mut self, a: A, b: B) {
|
|
if let (Some(a), Some(b)) = (self.valid_pos(&a), self.valid_pos(&b)) {
|
|
self.data.swap(a, b)
|
|
}
|
|
}
|
|
|
|
/// Return the count of neighbours (8 directions) matching predicate p
|
|
pub fn adjacent_count<C: AsCoord2d + Copy, P>(&self, c: &C, mut p: P) -> usize
|
|
where
|
|
P: FnMut(&T) -> bool,
|
|
{
|
|
self.adjacent_iter(c).filter(|i| p(i.value)).count()
|
|
}
|
|
|
|
/// Return the count of cardinal neighbours (4 directions) matching predicate p
|
|
pub fn cardinal_count<C: AsCoord2d + Copy, P>(&self, c: &C, mut p: P) -> usize
|
|
where
|
|
P: FnMut(&T) -> bool,
|
|
{
|
|
self.cardinal_iter(c).filter(|i| p(i.value)).count()
|
|
}
|
|
|
|
/// Return an iterator over the 8 neighbours of c. The iterator skips neighbouring positions outside of the grid.
|
|
pub fn adjacent_iter<'a, C: AsCoord2d + Copy>(&'a self, c: &'a C) -> OffsetsIter<'a, T> {
|
|
OffsetsIter {
|
|
grid: self,
|
|
origin: c.to_coord(),
|
|
cur: 0,
|
|
offsets: &ADJACENT_OFFSETS,
|
|
}
|
|
}
|
|
|
|
/// Return an iterator over the 4 cardinal neighbours of c. The iterator skips neighbouring positions outside of the grid.
|
|
pub fn cardinal_iter<'a, C: AsCoord2d + Copy>(&'a self, c: &'a C) -> OffsetsIter<'a, T> {
|
|
OffsetsIter {
|
|
grid: self,
|
|
origin: c.to_coord(),
|
|
cur: 0,
|
|
offsets: &CARDINAL_OFFSETS,
|
|
}
|
|
}
|
|
|
|
// fn window_compare_impl<const REV: bool>(&self, needle: &[T]) -> Vec<(i64, i64)> {
|
|
// if (self.width as usize) < needle.len() {
|
|
// return Vec::new();
|
|
// }
|
|
// let mut res = Vec::new();
|
|
// for y in 0..self.height() as i64 {
|
|
// let mut windows_tmp = self.row(y).unwrap().windows(needle.len());
|
|
// let windows = if REV {
|
|
// windows_tmp.rev()
|
|
// } else {
|
|
// windows_tmp
|
|
// };
|
|
|
|
// res.extend(
|
|
// windows
|
|
// .enumerate()
|
|
// .filter_map(|(x, w)| if w == needle { Some((x as i64, y)) } else { None }),
|
|
// );
|
|
// }
|
|
// res
|
|
// }
|
|
}
|
|
|
|
impl<T: BufRead> From<T> for Grid<u8> {
|
|
fn from(input: T) -> Grid<u8> {
|
|
let mut data = Vec::new();
|
|
let mut width = 0;
|
|
for line in input.split(b'\n').map(|i| i.unwrap()) {
|
|
if width == 0 {
|
|
width = line.len() as i64
|
|
} else if line.len() as i64 != width {
|
|
panic!("Grids must have fixed length rows")
|
|
}
|
|
data.extend_from_slice(&line);
|
|
}
|
|
Grid { data, width }
|
|
}
|
|
}
|
|
|
|
// Should be Grid<char>?
|
|
impl FromStr for Grid<u8> {
|
|
type Err = Box<dyn std::error::Error>;
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
Ok(Cursor::new(s).into())
|
|
}
|
|
}
|
|
|
|
// impl<T: Copy + Eq + PartialEq + Display + Debug + Into<char>> Display for Grid<T> {
|
|
// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
// for y in 0..self.height() {
|
|
// for x in 0..self.width() {
|
|
// f.write_fmt(format_args!("{}",self.get(x as i64, y as i64).unwrap() as char))?;
|
|
// }
|
|
// f.write_char('\n')?;
|
|
// }
|
|
// f.write_char('\n')
|
|
// }
|
|
// }
|
|
|
|
impl Display for Grid<u8> {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
for y in 0..self.height() {
|
|
for x in 0..self.width() {
|
|
f.write_fmt(format_args!(
|
|
"{}",
|
|
*self.get(&(x as i64, y as i64)).unwrap() as char
|
|
))?;
|
|
}
|
|
f.write_char('\n')?;
|
|
}
|
|
f.write_char('\n')
|
|
}
|
|
}
|
|
|
|
impl Display for Grid<bool> {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
for y in 0..self.height() {
|
|
for x in 0..self.width() {
|
|
f.write_fmt(format_args!(
|
|
"{}",
|
|
match *self.get(&(x as i64, y as i64)).unwrap() {
|
|
true => '.',
|
|
false => '#',
|
|
}
|
|
))?;
|
|
}
|
|
f.write_char('\n')?;
|
|
}
|
|
f.write_char('\n')
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
static TEST_VECTOR: &[u8] = b"ABCD
|
|
EFGH
|
|
IJKL
|
|
FBCG";
|
|
|
|
static TEST_VECTOR_S: &str = "ABCD
|
|
EFGH
|
|
IJKL
|
|
FBCG";
|
|
|
|
fn unchecked_load() -> Grid<u8> {
|
|
Grid::from(TEST_VECTOR)
|
|
}
|
|
|
|
#[test]
|
|
fn from_string() {
|
|
let grid = unchecked_load();
|
|
assert_eq!(grid.data, "ABCDEFGHIJKLFBCG".as_bytes());
|
|
assert_eq!(
|
|
TEST_VECTOR_S.parse::<Grid<u8>>().unwrap().data,
|
|
"ABCDEFGHIJKLFBCG".as_bytes()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn indexing() {
|
|
let grid = unchecked_load();
|
|
assert_eq!(grid.get(&(0, 0)), Some(b'A').as_ref());
|
|
assert_eq!(grid.get(&(3, 3)), Some(b'G').as_ref());
|
|
assert_eq!(grid.get(&(-1, 0)), None);
|
|
assert_eq!(grid.get(&(0, -1)), None);
|
|
assert_eq!(grid.get(&(5, 0)), None);
|
|
assert_eq!(grid.get(&(0, 5)), None);
|
|
}
|
|
|
|
#[test]
|
|
fn forward_slice() {
|
|
let grid = unchecked_load();
|
|
assert_eq!(grid.forward_slice(&(0, 0), 2), Some(b"AB".as_slice()));
|
|
assert_eq!(grid.forward_slice(&(2, 0), 2), Some(b"CD".as_slice()));
|
|
assert_eq!(grid.forward_slice(&(2, 0), 3), None);
|
|
assert_eq!(grid.forward_slice(&(0, 2), 4), Some(b"IJKL".as_slice()));
|
|
}
|
|
|
|
#[test]
|
|
fn row_iter() {
|
|
let grid = unchecked_load();
|
|
assert_eq!(
|
|
grid.row_iter(2).unwrap().collect::<Vec<_>>(),
|
|
[&b'I', &b'J', &b'K', &b'L']
|
|
);
|
|
assert!(grid.row_iter(-1).is_none());
|
|
assert!(grid.row_iter(4).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn col_iter() {
|
|
let grid = unchecked_load();
|
|
assert_eq!(
|
|
grid.col_iter(2).unwrap().collect::<Vec<_>>(),
|
|
[&b'C', &b'G', &b'K', &b'C']
|
|
);
|
|
assert!(grid.col_iter(-1).is_none());
|
|
assert!(grid.col_iter(4).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn find_all() {
|
|
let grid = unchecked_load();
|
|
assert_eq!(
|
|
grid.find_all(&b'G').collect::<Vec<_>>(),
|
|
[Coord2d { x: 2, y: 1 }, Coord2d { x: 3, y: 3 }]
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn neighbours_iter() {
|
|
let grid = unchecked_load();
|
|
assert_eq!(
|
|
grid.adjacent_iter(&(0, 1)).collect::<Vec<_>>(),
|
|
[
|
|
Item {
|
|
pos: Coord2d { x: 0, y: 0 },
|
|
value: &b'A'
|
|
},
|
|
Item {
|
|
pos: Coord2d { x: 1, y: 0 },
|
|
value: &b'B'
|
|
},
|
|
Item {
|
|
pos: Coord2d { x: 1, y: 1 },
|
|
value: &b'F'
|
|
},
|
|
Item {
|
|
pos: Coord2d { x: 0, y: 2 },
|
|
value: &b'I'
|
|
},
|
|
Item {
|
|
pos: Coord2d { x: 1, y: 2 },
|
|
value: &b'J'
|
|
},
|
|
]
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn cardinal_iter() {
|
|
let grid = unchecked_load();
|
|
assert_eq!(
|
|
grid.cardinal_iter(&(0, 1)).collect::<Vec<_>>(),
|
|
[
|
|
Item {
|
|
pos: Coord2d { x: 0, y: 0 },
|
|
value: &b'A'
|
|
},
|
|
Item {
|
|
pos: Coord2d { x: 1, y: 1 },
|
|
value: &b'F'
|
|
},
|
|
Item {
|
|
pos: Coord2d { x: 0, y: 2 },
|
|
value: &b'I'
|
|
}
|
|
]
|
|
)
|
|
}
|
|
|
|
// #[test]
|
|
// fn window_compare() {
|
|
// let grid = unchecked_load();
|
|
// assert_eq!(grid.window_compare(b"IJKL"), &[(0, 2)]);
|
|
// assert_eq!(grid.window_compare(b"BC"), &[(1, 0), (1, 3)]);
|
|
// assert_eq!(grid.window_compare(b"LF").len(), 0);
|
|
// }
|
|
}
|