grid: improve ergonomics with more trait impls and other improvements
update puzzles to pass tests, some performance gains
This commit is contained in:
parent
c213bbbc27
commit
447ff5c62c
18
src/day10.rs
18
src/day10.rs
@ -1,19 +1,19 @@
|
|||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
use aoc_runner_derive::{aoc, aoc_generator};
|
||||||
use grid::Grid;
|
use grid::Grid;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::io::{BufRead, Lines};
|
use std::io::BufRead;
|
||||||
|
|
||||||
#[aoc_generator(day10)]
|
#[aoc_generator(day10)]
|
||||||
pub fn get_input(input: &[u8]) -> TrailMap {
|
pub fn get_input(input: &[u8]) -> TrailMap {
|
||||||
TrailMap::from(input.lines())
|
TrailMap::from(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TrailMap {
|
pub struct TrailMap {
|
||||||
map: Grid<u8>,
|
map: Grid<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: BufRead> From<Lines<T>> for TrailMap {
|
impl<T: BufRead> From<T> for TrailMap {
|
||||||
fn from(input: Lines<T>) -> Self {
|
fn from(input: T) -> Self {
|
||||||
Self { map: input.into() }
|
Self { map: input.into() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -29,13 +29,13 @@ impl TrailMap {
|
|||||||
.collect_vec()
|
.collect_vec()
|
||||||
}
|
}
|
||||||
fn count_reachable_from(&self, pos: &(i64, i64), needle: u8, visited: &mut Grid<bool>) -> u64 {
|
fn count_reachable_from(&self, pos: &(i64, i64), needle: u8, visited: &mut Grid<bool>) -> u64 {
|
||||||
if visited.get(pos) == Some(true) {
|
if visited.get(pos) == Some(&true) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
visited.set(pos, true);
|
visited.set(pos, true);
|
||||||
}
|
}
|
||||||
let our_val = self.map.get(pos).unwrap();
|
let our_val = self.map.get(pos).unwrap();
|
||||||
if our_val == needle {
|
if *our_val == needle {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
// adjacents that are +1
|
// adjacents that are +1
|
||||||
@ -43,7 +43,7 @@ impl TrailMap {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|(x_ofs, y_ofs)| (pos.0 + x_ofs, pos.1 + y_ofs)) // get target position
|
.map(|(x_ofs, y_ofs)| (pos.0 + x_ofs, pos.1 + y_ofs)) // get target position
|
||||||
.map(|target_pos| (target_pos, self.map.get(&target_pos))) // get value at that position
|
.map(|target_pos| (target_pos, self.map.get(&target_pos))) // get value at that position
|
||||||
.filter(|(_, val)| *val == Some(our_val + 1)) // only interested if it's our value + 1
|
.filter(|(_, val)| *val == Some(&(our_val + 1))) // only interested if it's our value + 1
|
||||||
.map(|(pos, _)| pos) // discard the value
|
.map(|(pos, _)| pos) // discard the value
|
||||||
.map(|pos| self.count_reachable_from(&pos, needle, visited))
|
.map(|pos| self.count_reachable_from(&pos, needle, visited))
|
||||||
.sum()
|
.sum()
|
||||||
@ -51,14 +51,14 @@ impl TrailMap {
|
|||||||
|
|
||||||
fn count_paths_to(&self, pos: &(i64, i64), needle: u8) -> u64 {
|
fn count_paths_to(&self, pos: &(i64, i64), needle: u8) -> u64 {
|
||||||
let our_val = self.map.get(pos).unwrap();
|
let our_val = self.map.get(pos).unwrap();
|
||||||
if our_val == needle {
|
if *our_val == needle {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
[(-1, 0), (1, 0), (0, -1), (0, 1)] // left, right, up, down
|
[(-1, 0), (1, 0), (0, -1), (0, 1)] // left, right, up, down
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(x_ofs, y_ofs)| (pos.0 + x_ofs, pos.1 + y_ofs)) // get target position
|
.map(|(x_ofs, y_ofs)| (pos.0 + x_ofs, pos.1 + y_ofs)) // get target position
|
||||||
.map(|target_pos| (target_pos, self.map.get(&target_pos))) // get value at that position
|
.map(|target_pos| (target_pos, self.map.get(&target_pos))) // get value at that position
|
||||||
.filter(|(_, val)| *val == Some(our_val + 1)) // only interested if it's our value + 1
|
.filter(|(_, val)| *val == Some(&(our_val + 1))) // only interested if it's our value + 1
|
||||||
.map(|(pos, _)| pos) // discard the value
|
.map(|(pos, _)| pos) // discard the value
|
||||||
.map(|mov| self.count_paths_to(&mov, needle))
|
.map(|mov| self.count_paths_to(&mov, needle))
|
||||||
.sum::<u64>()
|
.sum::<u64>()
|
||||||
|
14
src/day12.rs
14
src/day12.rs
@ -1,5 +1,3 @@
|
|||||||
use std::io::BufRead;
|
|
||||||
|
|
||||||
use aoc_runner_derive::{aoc, aoc_generator};
|
use aoc_runner_derive::{aoc, aoc_generator};
|
||||||
use grid::{Coord2d, Grid};
|
use grid::{Coord2d, Grid};
|
||||||
|
|
||||||
@ -10,7 +8,7 @@ pub struct Farm {
|
|||||||
impl From<&[u8]> for Farm {
|
impl From<&[u8]> for Farm {
|
||||||
fn from(input: &[u8]) -> Self {
|
fn from(input: &[u8]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
map: Grid::from(input.lines()),
|
map: Grid::from(input),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,9 +25,9 @@ impl Farm {
|
|||||||
.fold((1, 0), |(area, perimeter), adj| {
|
.fold((1, 0), |(area, perimeter), adj| {
|
||||||
match self.map.get(adj) {
|
match self.map.get(adj) {
|
||||||
Some(plant) if plant == our_plant => {
|
Some(plant) if plant == our_plant => {
|
||||||
if visited.get(adj) == Some(false) {
|
if visited.get(adj) == Some(&false) {
|
||||||
// add the perimeter of the growth from there if not visited yet
|
// add the perimeter of the growth from there if not visited yet
|
||||||
let (add_area, add_perimeter) = self.compute_region(&adj, visited);
|
let (add_area, add_perimeter) = self.compute_region(adj, visited);
|
||||||
(area + add_area, perimeter + add_perimeter)
|
(area + add_area, perimeter + add_perimeter)
|
||||||
} else {
|
} else {
|
||||||
(area, perimeter)
|
(area, perimeter)
|
||||||
@ -99,9 +97,9 @@ impl Farm {
|
|||||||
.fold((1, self.count_corners(pos)), |(area, corners), adj| {
|
.fold((1, self.count_corners(pos)), |(area, corners), adj| {
|
||||||
match self.map.get(adj) {
|
match self.map.get(adj) {
|
||||||
Some(plant) if plant == our_plant => {
|
Some(plant) if plant == our_plant => {
|
||||||
if visited.get(adj) == Some(false) {
|
if visited.get(adj) == Some(&false) {
|
||||||
// add the perimeter of the growth from there if not visited yet
|
// add the perimeter of the growth from there if not visited yet
|
||||||
let (n_area, n_corners) = self.region_corners(&adj, visited);
|
let (n_area, n_corners) = self.region_corners(adj, visited);
|
||||||
(area+n_area, corners+n_corners)
|
(area+n_area, corners+n_corners)
|
||||||
} else { (area, corners) }
|
} else { (area, corners) }
|
||||||
}
|
}
|
||||||
@ -110,7 +108,7 @@ impl Farm {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn regions_discount_cost(&self) -> u64 {
|
fn regions_discount_cost(&self) -> u64 {
|
||||||
let mut visited = Grid::with_shape(self.map.width(), self.map.height(), false);
|
let mut visited = self.map.same_shape(false);
|
||||||
let mut cost = 0;
|
let mut cost = 0;
|
||||||
for y in 0..self.map.height() {
|
for y in 0..self.map.height() {
|
||||||
for x in 0..self.map.width() {
|
for x in 0..self.map.width() {
|
||||||
|
12
src/day6.rs
12
src/day6.rs
@ -3,14 +3,14 @@ use bitflags::bitflags;
|
|||||||
use rayon::iter::ParallelIterator;
|
use rayon::iter::ParallelIterator;
|
||||||
use rayon::slice::ParallelSlice;
|
use rayon::slice::ParallelSlice;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::{BufRead, Lines};
|
use std::io::BufRead;
|
||||||
use std::ops::BitAnd;
|
use std::ops::BitAnd;
|
||||||
|
|
||||||
use grid::Grid;
|
use grid::Grid;
|
||||||
|
|
||||||
#[aoc_generator(day6)]
|
#[aoc_generator(day6)]
|
||||||
pub fn get_input(input: &[u8]) -> Map {
|
pub fn get_input(input: &[u8]) -> Map {
|
||||||
Map::from(input.lines())
|
Map::from(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
@ -96,8 +96,8 @@ pub struct Map {
|
|||||||
path: Vec<((i64, i64), FacingDirection)>,
|
path: Vec<((i64, i64), FacingDirection)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: BufRead> From<Lines<T>> for Map {
|
impl<T: BufRead> From<T> for Map {
|
||||||
fn from(input: Lines<T>) -> Self {
|
fn from(input: T) -> Self {
|
||||||
let grid = Grid::from(input);
|
let grid = Grid::from(input);
|
||||||
let mut visited_from: Grid<DirectionSet> = Grid::new(grid.width() as i64);
|
let mut visited_from: Grid<DirectionSet> = Grid::new(grid.width() as i64);
|
||||||
visited_from.data.resize(grid.data.len(), DirectionSet::empty());
|
visited_from.data.resize(grid.data.len(), DirectionSet::empty());
|
||||||
@ -114,7 +114,7 @@ impl<T: BufRead> From<Lines<T>> for Map {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Map {
|
impl Map {
|
||||||
fn look(&self, dir: &FacingDirection) -> Option<u8> {
|
fn look(&self, dir: &FacingDirection) -> Option<&u8> {
|
||||||
self.grid.get(&dir.pos_ofs(self.guard_pos))
|
self.grid.get(&dir.pos_ofs(self.guard_pos))
|
||||||
}
|
}
|
||||||
/// Move one step in the facing direction, return if we are still inside the bounds
|
/// Move one step in the facing direction, return if we are still inside the bounds
|
||||||
@ -132,7 +132,7 @@ impl Map {
|
|||||||
}
|
}
|
||||||
self.visited_from.set(
|
self.visited_from.set(
|
||||||
&new_pos,
|
&new_pos,
|
||||||
self.visited_from.get(&new_pos).unwrap() | self.guard_facing.into(),
|
*self.visited_from.get(&new_pos).unwrap() | self.guard_facing.into(),
|
||||||
);
|
);
|
||||||
self.guard_pos = new_pos;
|
self.guard_pos = new_pos;
|
||||||
StepOutcome::Continue
|
StepOutcome::Continue
|
||||||
|
@ -2,19 +2,19 @@ use aoc_runner_derive::{aoc, aoc_generator};
|
|||||||
use grid::Grid;
|
use grid::Grid;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::io::{BufRead, Lines};
|
use std::io::BufRead;
|
||||||
|
|
||||||
#[aoc_generator(day8)]
|
#[aoc_generator(day8)]
|
||||||
pub fn get_input(input: &[u8]) -> AntennaMap {
|
pub fn get_input(input: &[u8]) -> AntennaMap {
|
||||||
AntennaMap::from(input.lines())
|
AntennaMap::from(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AntennaMap {
|
pub struct AntennaMap {
|
||||||
map: Grid<u8>,
|
map: Grid<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: BufRead> From<Lines<T>> for AntennaMap {
|
impl<T: BufRead> From<T> for AntennaMap {
|
||||||
fn from(input: Lines<T>) -> Self {
|
fn from(input: T) -> Self {
|
||||||
Self { map: Grid::from(input) }
|
Self { map: Grid::from(input) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fmt::{Debug, Display, Formatter, Write},
|
fmt::{Debug, Display, Formatter, Write},
|
||||||
io::{BufRead, Lines},
|
io::BufRead,
|
||||||
iter::repeat,
|
iter::repeat,
|
||||||
|
mem::swap,
|
||||||
ops::{Add, Sub},
|
ops::{Add, Sub},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,6 +50,21 @@ impl AsCoord2d for Coord2d {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsCoord2d for (i32, i32) {
|
||||||
|
fn to_coord(self) -> Coord2d {
|
||||||
|
Coord2d {
|
||||||
|
x: self.0.into(),
|
||||||
|
y: self.1.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn x(&self) -> i64 {
|
||||||
|
self.0.into()
|
||||||
|
}
|
||||||
|
fn y(&self) -> i64 {
|
||||||
|
self.1.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AsCoord2d for (i64, i64) {
|
impl AsCoord2d for (i64, i64) {
|
||||||
fn to_coord(self) -> Coord2d {
|
fn to_coord(self) -> Coord2d {
|
||||||
Coord2d { x: self.0, y: self.1 }
|
Coord2d { x: self.0, y: self.1 }
|
||||||
@ -76,19 +92,41 @@ impl AsCoord2d for (usize, usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsCoord2d for (u64, u64) {
|
||||||
|
fn to_coord(self) -> Coord2d {
|
||||||
|
Coord2d {
|
||||||
|
x: self.0 as i64,
|
||||||
|
y: self.1 as i64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn x(&self) -> i64 {
|
||||||
|
self.0 as i64
|
||||||
|
}
|
||||||
|
fn y(&self) -> i64 {
|
||||||
|
self.1 as i64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Grid<T> {
|
pub struct Grid<T> {
|
||||||
pub data: Vec<T>,
|
pub data: Vec<T>,
|
||||||
width: i64,
|
width: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Copy + Eq + PartialEq + Display + Debug> Grid<T> {
|
impl<T: Clone + Eq + PartialEq + Display + Debug> Grid<T> {
|
||||||
pub fn new(width: i64) -> Self {
|
pub fn new(width: i64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
data: Vec::new(),
|
data: Vec::new(),
|
||||||
width,
|
width,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Returns a new [Grid] with the same shape (width x height) as `self`, filled with `fill`
|
||||||
|
pub fn same_shape<NT: Clone + Eq + PartialEq + Display + Debug>(&self, fill: NT) -> Grid<NT> {
|
||||||
|
Grid {
|
||||||
|
data: Vec::from_iter(repeat(fill).take(self.width() * self.height())),
|
||||||
|
width: self.width,
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn with_shape(width: usize, height: usize, fill: T) -> Self {
|
pub fn with_shape(width: usize, height: usize, fill: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
data: Vec::from_iter(repeat(fill).take(width * height)),
|
data: Vec::from_iter(repeat(fill).take(width * height)),
|
||||||
@ -96,10 +134,10 @@ impl<T: Copy + Eq + PartialEq + Display + Debug> Grid<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn width(&self) -> usize {
|
pub fn width(&self) -> usize {
|
||||||
return self.width as usize;
|
self.width as usize
|
||||||
}
|
}
|
||||||
pub fn height(&self) -> usize {
|
pub fn height(&self) -> usize {
|
||||||
return self.data.len() / self.width();
|
self.data.len() / self.width()
|
||||||
}
|
}
|
||||||
fn pos<C: AsCoord2d>(&self, c: &C) -> i64 {
|
fn pos<C: AsCoord2d>(&self, c: &C) -> i64 {
|
||||||
c.y() * self.width + c.x()
|
c.y() * self.width + c.x()
|
||||||
@ -124,18 +162,17 @@ impl<T: Copy + Eq + PartialEq + Display + Debug> Grid<T> {
|
|||||||
}
|
}
|
||||||
self.pos(c).try_into().ok()
|
self.pos(c).try_into().ok()
|
||||||
}
|
}
|
||||||
pub fn get<C: AsCoord2d>(&self, c: &C) -> Option<T> {
|
pub fn get<C: AsCoord2d>(&self, c: &C) -> Option<&T> {
|
||||||
match self.valid_pos(c) {
|
match self.valid_pos(c) {
|
||||||
Some(pos) => Some(self.data[pos]),
|
Some(pos) => Some(&self.data[pos]),
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set<C: AsCoord2d>(&mut self, c: &C, val: T) -> Option<T> {
|
pub fn set<C: AsCoord2d>(&mut self, c: &C, mut val: T) -> Option<T> {
|
||||||
match self.valid_pos(c) {
|
match self.valid_pos(c) {
|
||||||
Some(pos) => {
|
Some(pos) => {
|
||||||
let res = Some(self.data[pos]);
|
swap(&mut self.data[pos], &mut val);
|
||||||
self.data[pos] = val;
|
Some(val)
|
||||||
res
|
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
@ -192,17 +229,17 @@ impl<T: Copy + Eq + PartialEq + Display + Debug> Grid<T> {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: BufRead> From<Lines<T>> for Grid<u8> {
|
impl<T: BufRead> From<T> for Grid<u8> {
|
||||||
fn from(input: Lines<T>) -> Grid<u8> {
|
fn from(input: T) -> Grid<u8> {
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
let mut width = 0;
|
let mut width = 0;
|
||||||
for line in input.map(|i| i.unwrap()) {
|
for line in input.split(b'\n').map(|i| i.unwrap()) {
|
||||||
if width == 0 {
|
if width == 0 {
|
||||||
width = line.len() as i64
|
width = line.len() as i64
|
||||||
} else if line.len() as i64 != width {
|
} else if line.len() as i64 != width {
|
||||||
panic!("Grids must have fixed length rows")
|
panic!("Grids must have fixed length rows")
|
||||||
}
|
}
|
||||||
data.extend_from_slice(line.as_bytes());
|
data.extend_from_slice(&line);
|
||||||
}
|
}
|
||||||
Grid { data, width }
|
Grid { data, width }
|
||||||
}
|
}
|
||||||
@ -224,7 +261,7 @@ impl Display for Grid<u8> {
|
|||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
for y in 0..self.height() {
|
for y in 0..self.height() {
|
||||||
for x in 0..self.width() {
|
for x in 0..self.width() {
|
||||||
f.write_fmt(format_args!("{}", self.get(&(x as i64, y as i64)).unwrap() as char))?;
|
f.write_fmt(format_args!("{}", *self.get(&(x as i64, y as i64)).unwrap() as char))?;
|
||||||
}
|
}
|
||||||
f.write_char('\n')?;
|
f.write_char('\n')?;
|
||||||
}
|
}
|
||||||
@ -236,13 +273,13 @@ impl Display for Grid<u8> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
static TEST_VECTOR: &str = &"ABCD
|
static TEST_VECTOR: &[u8] = b"ABCD
|
||||||
EFGH
|
EFGH
|
||||||
IJKL
|
IJKL
|
||||||
FBCG";
|
FBCG";
|
||||||
|
|
||||||
fn unchecked_load() -> Grid<u8> {
|
fn unchecked_load() -> Grid<u8> {
|
||||||
Grid::from(Cursor::new(TEST_VECTOR).lines())
|
Grid::from(TEST_VECTOR)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -254,8 +291,8 @@ FBCG";
|
|||||||
#[test]
|
#[test]
|
||||||
fn indexing() {
|
fn indexing() {
|
||||||
let grid = unchecked_load();
|
let grid = unchecked_load();
|
||||||
assert_eq!(grid.get(&(0, 0)), Some(b'A'));
|
assert_eq!(grid.get(&(0, 0)), Some(b'A').as_ref());
|
||||||
assert_eq!(grid.get(&(3, 3)), Some(b'G'));
|
assert_eq!(grid.get(&(3, 3)), Some(b'G').as_ref());
|
||||||
assert_eq!(grid.get(&(-1, 0)), None);
|
assert_eq!(grid.get(&(-1, 0)), None);
|
||||||
assert_eq!(grid.get(&(0, -1)), None);
|
assert_eq!(grid.get(&(0, -1)), None);
|
||||||
assert_eq!(grid.get(&(5, 0)), None);
|
assert_eq!(grid.get(&(5, 0)), None);
|
||||||
@ -271,11 +308,11 @@ FBCG";
|
|||||||
assert_eq!(grid.forward_slice(&(0, 2), 4), Some(b"IJKL".as_slice()));
|
assert_eq!(grid.forward_slice(&(0, 2), 4), Some(b"IJKL".as_slice()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// #[test]
|
||||||
fn window_compare() {
|
// fn window_compare() {
|
||||||
let grid = unchecked_load();
|
// let grid = unchecked_load();
|
||||||
assert_eq!(grid.window_compare(b"IJKL"), &[(0, 2)]);
|
// 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"BC"), &[(1, 0), (1, 3)]);
|
||||||
assert_eq!(grid.window_compare(b"LF").len(), 0);
|
// assert_eq!(grid.window_compare(b"LF").len(), 0);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user