Compare commits

...

9 commits

Author SHA1 Message Date
41b013b5e9 day 24 made quicker, minor improvements 2023-08-13 16:51:24 +02:00
91b264ba69 day25 finished 2023-08-13 15:08:31 +02:00
abc1530bf3 day 24 finsihed 2023-08-13 12:26:59 +02:00
62cb729aee day 23 finished 2023-08-12 16:54:10 +02:00
80d724a6f6 day22 finished 2023-08-12 10:34:53 +02:00
88be9a39a0 Improve Pos 2023-08-08 20:28:37 +02:00
651ccb9cba better Pos 2023-08-07 05:50:26 +02:00
bcefb1b68f minor improvements 2023-08-05 20:38:05 +02:00
4e64b9786d day21 Finished 2023-08-05 19:40:25 +02:00
51 changed files with 4967 additions and 10548 deletions

View file

@ -4,6 +4,6 @@ These are my solutions for [Advent of Code 2022](https://adventofcode.com/). Tha
If you look at the code and see ways I could improve it, please do not hesitate to contact me. I am always grateful for everything that makes me a better programmer.
Also, if you ever look for a programmer and think my code is any good, please also contact me at once.
Also, if you ever look for a programmer and think my code is any good, please consider contacting me.
All code is published under the [Unlicense](https://unlicense.org/)

15
data/day21/example01.txt Normal file
View file

@ -0,0 +1,15 @@
root: pppw + sjmn
dbpl: 5
cczh: sllz + lgvd
zczc: 2
ptdq: humn - dvpt
dvpt: 3
lfqf: 4
humn: 5
ljgn: 2
sjmn: drzm * dbpl
sllz: 4
pppw: cczh / lfqf
lgvd: ljgn * ptdq
drzm: hmdt - zczc
hmdt: 32

1579
data/day21/input.txt Normal file

File diff suppressed because it is too large Load diff

14
data/day22/example01.txt Normal file
View file

@ -0,0 +1,14 @@
...#
.#..
#...
....
...#.......#
........#...
..#....#....
..........#.
...#....
.....#..
.#......
......#.
10R5L5R10L4R5L5

202
data/day22/input.txt Normal file

File diff suppressed because one or more lines are too long

7
data/day23/example01.txt Normal file
View file

@ -0,0 +1,7 @@
....#..
..###.#
#...#.#
.#...##
#.###..
##.#.##
.#..#..

6
data/day23/example02.txt Normal file
View file

@ -0,0 +1,6 @@
.....
..##.
..#..
.....
..##.
.....

70
data/day23/input.txt Normal file
View file

@ -0,0 +1,70 @@
###.###..#.....#..#.##..##.#...###.#####..#.##.#####.###..######.#.#.#
....##.#.#.#......#.###..##..###...#.#...####.##...#.#....#.##.##....#
#..#.#.#.##.........#####....#..##.......#.#.#.##....#.#....##..##..##
#.##.##....#.##.#.###.#...#..#.##.####.#.#.#.#.###.###.####....#.#.#.#
.#.#.#####.##...##.##..#..#.#..####.#..#.#.#######..#.##.#.###.#.....#
..#..#....###.#....###.##.#..#..#.#..#.#.#...##...#....##....#..##...#
###..........#..#.###....##.##...#####.###.#.##...#.##.....#.##.#.####
#.###.####...#..#.##.....#.####.##..##...#..##.....#...###..##.######.
.....#.####..##.#...#..##.###..#.##.####..#...#.###..##..#.#.###...#.#
.#.#.#..##.##..#.#.##.#.##.#.##..##.##.#..##.#.##..#####.#.###.#.##..#
###.#.###.##.##....#.#.#...##...#.#..#.#..#.#..##.#....#####...#...###
.#.#.#.#.#..#....###.#.##.#.#.####..#.#.#...#.#.#.###....#....#...##..
#.#....#........#....######...#....#..#.###...#.....###..###...##..#..
.###.##....#.#.#.###..###....#.#.#..###.##..#..#..#####.#.###.#..##..#
..###.##..####.....#...##.#....#...###..#.###.#...##....####........#.
#####.##..#..##..###....#.##....#.#...##.########...#.#.#..##..##..###
.##..#...##.....###.#...#..###.######.#.#...#...##..#.....#..#.##..#.#
.#####..#.######.#..#..#.#.#.##....##..#.......#....#.##.#.#..##...#.#
.#.##..#..###..#.######........##.##.##..#....#.###.######.##.#.#..##.
##.##.#..####.#....###.#..##..#...#.#####.#..#...#....##...#.###..#.##
#....##..#...#.#.###.#.#...##.#.#..###...####..##.#..##.#.#####.....##
.##.#..#.##.##.#.##.....#######..#...#.##.####...#.....#.##..#...##...
#.#.#.#####...#####..#.##....#.#.####.#..#.##..#.#.#...#..##.#...##...
#.#.#.#..#.#.#..#..###.#.....#.#.#..#.#####.#.#..#...#.###.###...#.#..
.####..####.#.#..#...##.##.#....#.#.....#.##.###..#...###.###.#....##.
....##.#.##.###.#..##.#..#.......##...#.###...#.......#..####.#.#.####
....#.#......####..#.#..#..#..#.##..#..#..###..#.#.##.#.##..###..#..#.
.##.#.....##.#..#..#..####..#.#.#...###.##.##.##...#..##..##.##...##..
#..####..#...#.#....##...##..#.....##.#######..#.####...##..######.#..
..#.##.##....##.#######..##...#..#..###.....##.#.#..#.#...##..###.####
#####......##..#.#..#......#..####.####...#####..######...##.###..##..
###..##.#.##....##..######.#.#.#......#..##.#.##.#.##...#.##...###..##
.....#.#.#.##..#.#####.##.#.#.....#.#..#.###.##..#.###.##..##.###....#
.###.####..####.###....####.#.##..##..#.....##.###.##.######.#..#.#...
.#.#######.#.#.###....#####..#.#.#..#.#.#.#.#..##..#..#...##..##..#.#.
.##..####..#.#...###.#...#....####...###.#..#.#.....#.#.##.#..##.#.#..
.#.#.#.#####..##.####..#..##......#..#.###.#..#....#...#....#...#.####
..##.##.###.#..#..#.#..#.###......##.#..#..#.#.####.#.#.#.####.##.....
##.#..#..##.#######..#..#....##..#...########.##.#.#...##...##.##.....
.#...#####.#..##.##.###.####.....###.#......########.#..##.##.#....###
...####.###....##..#..#....#..##.####..##..#..###.......#.#.###.##...#
.#.#......#####...##....#.#..#######.######.#.#.##.#..###..#....##..#.
..###.#.#####..##.#.####....#.###..#..#.##.#####.##..##.##..##..#.##.#
.####.#.######.###.###..#......#...#...#..#..##.###.##.#.####.#.#.##.#
#####.###.##.#########..###..#.#.##.#..#..##...#.###.#.#......###..##.
###...###.##.#.###.#.##.####.#######..##.#....#####.###.#..#.#...###..
.######...#...#..###......#.#.##........#..##.#.#.##.#....###.....##.#
#.#.....##.##.##..##..##.#.##.###.##.###.##.#.##.###....##..#.#.##....
#.####.##...##....#.#.....#.#.###..#..####.....#.##..##...##.###.#..##
#####.##.#...#....#..###.##..##...#....#....##.#.#....#####....####.##
####...##...#.###.##.##.#####...#.....##..#####...########..####......
#.#..##.##.#.######.###.#.##.......#......####..##.#.#.#..##.###.#..#.
#..#..########..##..#.######..#.#...#..#.#####.###.####......##..###.#
#..#.#...#..#....#..##.##.#..###.####.###########.#..#.###.#.##.###..#
##.#..#..#.....#.##.#..######..#..#...#.#...###.#.#.#.#.#.########...#
..###.#.##......###..#...##......#####.#.#....####.....##.#..#.#..#.#.
..######....#..##.#.#..#.##.#..##.##.#....##....###.#.##.##..##....###
.#####....##....###.#.#######.....##.###.#..#.#.#..###...#.##..#..#.#.
#..#####...#..##..#..#..#....#.##....#..##.......##..#....#.#..#.#.###
#.#..#####...#...#...##.#....#..##.#.##....#####.....#.##.....#..#.###
##.#.#....#...#.####....###....#...#.##...##.#....#.#.#.##......##.###
...##.#.#..#.###..#.#...##.#...##.#...##...#..###.#.###...###..#..##.#
.#####...#.#..###.#.##.##.#.##..##.#.#.....##....#.#..#.#.#..#####.###
#.####.####..###......##.....#.#.....##.#..##.##.#....###.....####..##
#..#####...#....#...###..##...###..####..####..#...#####.#####..#.###.
####.###.###....####....##..#..####...#....###.##.#.##...##.......####
.#.###....####..#..#.###.##.##.####..##.#..###..#..#.#.#..#..#.##..##.
....##......####.####.######...###.....###.#.#..##.#.##.......#...#...
##.###.##...#.....#.##.#.......####.##.###..###.#.#..#.##..#.####....#
....#..##.#...##.##.#.#.#..#.#..#..#.####....#####..#.....#####.#.....

6
data/day24/example01.txt Normal file
View file

@ -0,0 +1,6 @@
#.######
#>>.<^<#
#.<..<<#
#>v.><>#
#<^v^^>#
######.#

7
data/day24/example02.txt Normal file
View file

@ -0,0 +1,7 @@
#.#####
#.....#
#>....#
#.....#
#...v.#
#.....#
#####.#

37
data/day24/input.txt Normal file
View file

@ -0,0 +1,37 @@
#.####################################################################################################
#>^v^v>><.>>><>^v<>^v>v<>^<<>>v>>.>><v>^<>^.<>v.>v^>vvvv<<<<>>^v^^^>><><.^^^.<<<>><^v^vv^v<.v.v>>.vv<#
#>^><>^v^<>.<v><.<.v<v>vv<^<vvvv.^^...<<<^^vv<<v<>>>>v^<>>^^^><.>>>>v<<>^.>.^^<v<.><<.<><.>>>^^^>>.<>#
#<.vvv^^v>^<<^^^>.^<..v.v<<^>vv<>>>v^>vv<><v<<vv<v^>v<^^^^v>.<.vv><v>v<v<^v^>>^<^^<>^>v^<^.^^v><>.><>#
#>..<^^<>>v>><>vv^<v.^v.>^<.v^<v><<^<><v^v<.vv^>>vvv.vvv<>v>><<.>^><v^<<<v<<>><v><^v.v.<>^>v><v<^<<.>#
#>^^<v>v><^<^>^<vvv^.>^^><^>><^^.vv>v<>v<>^>^v><v>>.>>^<v><^v^v^>v^<.<v^^^<>vvv^^<.<v<>>>.^>><<.vvv.>#
#><v>v^>^<>v^>>^^<>.><^v<^vvv>vv<.><>v>v<..<^>>v^v<<<<v>v^.<<<^^^^.^v^<<>^vvv>^><.^v<^<>vv><<^<>^<^<<#
#.^vvv<vv>^^v<>v<<v^^>.<<<v><<vv<v>>^<vv<v>><<^>^vv<<>^<<>^v.>>.<>>^<^<v<^^^<.vv^vv><^><v^^v>v^v>^vv.#
#<v.>^<^><^..>^.>>v>>vv<^<.<vv<<v^v<.vvv>>.^v^^<^v<^<.^vv>v>^><v<v^>^v>>>>^^^v>^^>>>v<v^vv<.^><vv<>^<#
#><>v^>^v<.<.>.<^.<>>^^>v^<v^<v>vvv<<^>vv<^^>^<^>v<.^.v<<.v.v^v>v><>..^^<.v^^<.<<<<<><>v><v<^^v<^<vv<#
#><<>v><>^^^.<.<<>^v>v^>^>>>>v>.^>.v>.^>^.v<^<>v.v>v^v^^>^v>>>.<^v^^.>>>>^<<v<.^^>>>>^><v^v>vvv^>^><<#
#><vvvv.>v^.v>>><>^>><><v^<.>.^<^v.>.^<^>>^v<.<^^v<^><..<vv^v<>vv<<vv^v.>>^.<v^>^>^<^<^>><><v<<>.><<>#
#>.<^^v^><<<.v<vv<>^<>v^vv<^^.<>>.^<vv>>^.v.>^><v^^>.<<>v<>^v<v><v^^<v.v><<>vvv.v<>v>vvv^<>v^>^v<<<><#
#.>><<vv><>vv>v..<<^^vv^>^<>^^v>v^vv>.><>^<vv^^<v><^^>><>>v<<v>^v^^><>.^.vv>v.^^>^vv<><.>>^^<^<><>v>.#
#><<v>v<><^<<^><..<^>v<<^.^^<.^^v<>>^v>.^^^<<v>v>><><.<>>^v<^.v<vv<<^>>>vv^<vv.>^^^<><>v.>.^<^v<vv^><#
#<.>vvv<v>^.vv.>.<v^.<..<>^v<>>^^vv>.>^v^>>>vv^<v><^v.v^>>>>><>v>v<>v^^>.^^v>^v><<>vv^<><>>v<^<v<<<<>#
#<v<><^v<>>.<v^><<>^>v<<.v^v.<^v.^<vv<.v.^<v><><><v^^>v<>.vv^>vv<<^^<^^^><<>^>.^v<<<^<^<^^>>vv.<v.v<.#
#<>vv^^<<^^^v^^<v^vvv^>^^.vvv^^^^v^>^^^v<^v><<^^^.<><v.vv<<^v<^>^><.^><^<>.v>^vv^v<<^<.<^.^>vv>^v>^<.#
#<.vv>><.<vv<^^^<.^v><>>>><<^^<><^<vv<<>^v..>v<^^v><^><<^^^<<>^^>.>v><^<v<><v>v^<v<v^v.vv>v^<<<v>.^>>#
#<^vv<<v^><>^^<<^.<v^>^vv.><^>v.>^vv.<^v^^<v<<v<><^<>v<<v<<><>^v^>v^^^><^>>^.<v.^<.^^.v<^v<>>.<<<v>^<#
#>>..^^^<v.v<>>^^<v^..^vv<>.<>>>^><>^<v><<.v.^v^^<^<><>..>^<>.>^^<v.^v<v<<^v>^vv.v>.<.^^<v^<<>^^<^v<.#
#>>vvv^v^v^>v<<vv.^.^<<vv^>^>v<.^>^<v^>^^<^<>^.><^v<<<v>>..v><>v^>^>><^^<>.v<vv^>>>>v<^v.v>v.>^vv^.^<#
#<^<>.^<>>^<^v>.^v.><.<v.v.^v><^>.>^<>^^>>^^^.<^<>><>^><vvv^>^.v^^v>vv<<vvv>^.^>><.<>^.^<>.>>>v>v<v^>#
#<^^vv<<^<^.<^>^<vv.>>.>><^<^^^<<>.v>v>v>v.^<<v<.<>v<<<>v<>^<v.^<<<v<<>>>>.^><^^<>>>^<><^.^.v^^v^^>..#
#..v>vv^<vv>><^>^^^<v>><v^^><>.^^><^<><>^>><v>^v<><v.^<>><.>^>v^>^v<><v^v>><><^>v>.v^v>v<><.^^.>.v.^<#
#.<v>v<^v.<<<vv<>^v^vv<^v^>^^v^<>^>>vv<^^v^^^^<.^v<>^<<^<v^v<><v>v^<<>>.vvv.>>^v>>>vv<<><^v<v^v<<.>>>#
#<<vv.v^.>..v><v<><vv<<<<.>vv>v>>^v^v<v^<v<>v<<v^v^v^<<v^>v<^^^>v<^^^^^>>><<v<>v<>.>v^v<><.<<v<>^.^^.#
#>>>>>^^.><^<>^<>v>.<^vv^^<>>^v><^<^>vv^<v><.>^v>.v<v<>>>>^.^>^v^^^<vvv^.<<..^v<.>^><<>><<^<.<<v<>^v<#
#<.v.^>.^<.v<^^<.<^<<^.^<>><^<<^<><.<>.><<<><^v<v>^^v<<v>><^<>>^>^^>vv>^<.^<v><>.><v^v<vv^<^^<v>>>^v<#
#<>v^>^^v>>^<^>v>>>.>^.><>..>^v>><>v<>^>.^v<<<<^>v>v><v^<^>^^>^<<>^>vv><<<v>v^^v<vv<^<^>vv^<.v^<^v>^>#
#><<vvv.<<<vv<vv^^^v^v<^>vv^<>^^v<>v<^^^^><>^^>^<<<>v.vv^<><<><.^<<<^>>^vv<.v^^>vv><<<>v.<><v^><^^^v.#
#<><v<v><^<^^vvv^>><><<<.<<vv<^<>.^<>>^v^>>vvv>>v..><<^^.v<><^^><^>>^<<v<<^v^>v>vvvvv...^<v^<^<v<.^<.#
#>><<<v^^v^>^<<.>>vvv^v<^<>.^v^<^v<^^>>v<.>v<v..^^v.^>.v>^>>v^<<.v<^<..>.v<<..^^^v>v<.^<.>v^v^<v^v>^>#
#<^>v<>^<<v>>vv>.><^v<^.><<^v>v<^<<<v^.^v.><>v><>^v><<<v.>vv<>vv>><>>^><><v>>v^.^v<<v^vvv<.<<>.v^^v<>#
#<^<^><<>v..>v^>v>.v<<<^<<<v^v<<>^<.<.<v.v^<><v^<<>^><vv.v>>vv^.v^.^^>^^<<^^>.vv<.>.^^<>.<<v><vv<<>>>#
#>v>>vv<.v<v.<^.^^><><^^v>^>>>>^>vv><<v>vv^.>>v^^vv^v^.>^v<>>>^^^>..vv^.^><..>>v<>^>^^<<.v<>v^^^v>^v>#
####################################################################################################.#

13
data/day25/example01.txt Normal file
View file

@ -0,0 +1,13 @@
1=-0-2
12111
2=0=
21
2=01
111
20012
112
1=-1=
1-12
12
1=
122

135
data/day25/input.txt Normal file
View file

@ -0,0 +1,135 @@
1--
1-
1--02
1122-=2-==0-0=1-2
2=1-==-1022=10
1==-02211022=000
101==210-21=2
11010==0==221=0=02
11=1101020=02-11
1===
10-2
10=02=-=02
1-1--112-202--0
11-=-0-002=0112-1
2--0-2=02=-22
1001-2
1=101=2=2102-201--1
1-2==1100--
2-01=--=1-11
101-=0010-210==
1=-21-022121110
1==21
22==1
2-0-=2=1
2--0=10-00--2-
1--1=011-2
1-100-2
11-1
112
222200110-11110-2=
1==2-210-1122=-2022
2==1-20-2-=-21
21=1-=0=-1-=-=-12
210---=-1===-===00
1--21=01-022=2-
11-=
21-2
2-22-0
1==-
1-----01
1-0-21-
211212-22-12--=2
1=220=10=20200-0
21-=11-==20=1
102-2==2-==-0
1=0=01-
22=1--0111
1-----=1==00===0-
20==-1-121-
1=--2--21-202=21
1-0122=--=
2==-2--===-=-1=-012
2220=-00-10-
1==0=---10212-1-
1221=-022120
100112
2-=0=220-=01-1=12-
1=00
2122121=2
2-==
12-21===10220
1=0
22-1=--01-==0
1-2-2202=1=02
11-===21-
1-22=0102-=-222-1
1021=0=21=0=11=2-2
1===2222=0-0=
20=2-=0=
1=2==02-1-100200
1=1211211----
12=1=1---=20
2--2=2=1100020200
1=0-002-11-10----
1=1=-1=-0
1-21=1=02=21=20
1-=0-002-0=--=0=
20-===-1=
220021012
12===1001=--=2=1-
1=-02
21
2=200222122
1-2=110111=012=1-
1=02=
101==1-=1=0001
1=22--1-021
120
201-0211
1-1012211--2=-1=
12=10202=20
11-22=2=2=-0==1=00
11=-
12-1--2222
22
1120--0==
10=-0=001-
1=00200200=
1-20
1-0-
10112-202=-011
11-2=
1-=10=1-0=-21==11200
11
12-0-=01--12
1-=1121-=0
120-21=-1=10121=
1102-22
21=
1==--1==---1-==
12=0--=2210-1==
22210=1-=0
1102-20-1
1=1=2102
11--=2==-0
2=0=1=
21-0-10=
12-212-2=-==102--
1==0100210-=
12210-0-1
1=0-2-0=
10-1=211=1-2=
201-0--
1--2=00=-1-211
121=-22100=210--
122-0-1-
2=211
2=1-=2-2-0001=-1
1102--11=0=1=10==2
1==01121-2--0=12
2=21-20=-0-0
2-212=21-02=1-2-02
2=1-0
10-0-=20
2=2=201

5005
rest.txt

File diff suppressed because it is too large Load diff

5001
result.txt

File diff suppressed because it is too large Load diff

49
src/common/abs.rs Normal file
View file

@ -0,0 +1,49 @@
pub trait Abs {
fn abs(&self) -> Self;
fn abs_beween(&self, other: &Self) -> Self;
fn is_negative(&self) -> bool;
}
macro_rules! signed_impl {
($($t:ty)*) => ($(
impl Abs for $t {
#[inline]
fn abs(&self) -> $t {
if self.is_negative() { -*self } else { *self }
}
#[inline]
fn abs_beween(&self, other: &Self) -> Self {
(*self - *other).abs()
}
#[inline]
fn is_negative(&self) -> bool {
*self < 0
}
}
)*)
}
signed_impl!(isize i8 i16 i32 i64 i128);
macro_rules! unsigned_impl {
($($t:ty)*) => ($(
impl Abs for $t {
#[inline]
fn abs(&self) -> $t {
*self
}
#[inline]
fn abs_beween(&self, other: &Self) -> Self {
if *self >= *other {
*self - *other
} else {
*other-*self
}
}
#[inline]
fn is_negative(&self) -> bool {
false
}
}
)*)
}
unsigned_impl!(usize u8 u16 u32 u64 u128);

View file

@ -3,25 +3,25 @@ use std::fmt::Display;
use num_traits::{Num, NumAssignOps};
use super::pos::Pos;
use super::pos2::Pos2;
#[derive(Debug, Clone, Copy, Default)]
pub struct Area<T>
where
T: Num,
{
lower_left: Pos<T>,
upper_right: Pos<T>,
lower_left: Pos2<T>,
upper_right: Pos2<T>,
}
impl<T> Area<T>
where
T: Num + Ord + Copy,
{
pub fn new(p1: Pos<T>, p2: Pos<T>) -> Area<T> {
pub fn new(p1: Pos2<T>, p2: Pos2<T>) -> Area<T> {
Area {
lower_left: p1.min_components(&p2),
upper_right: p1.max_components(&p2),
lower_left: p1.min_components(p2),
upper_right: p1.max_components(p2),
}
}
}
@ -30,25 +30,25 @@ impl<T> Area<T>
where
T: Num + Ord + Copy,
{
pub fn extend(&self, pos: Pos<T>) -> Area<T> {
pub fn extend(&self, pos: Pos2<T>) -> Area<T> {
if self.contains(pos) {
return *self;
}
Area {
lower_left: self.lower_left.min_components(&pos),
upper_right: self.upper_right.max_components(&pos),
lower_left: self.lower_left.min_components(pos),
upper_right: self.upper_right.max_components(pos),
}
}
pub fn get_lower_left(&self) -> Pos<T> {
pub fn get_lower_left(&self) -> Pos2<T> {
self.lower_left
}
pub fn get_upper_right(&self) -> Pos<T> {
pub fn get_upper_right(&self) -> Pos2<T> {
self.upper_right
}
pub fn contains(&self, pos: Pos<T>) -> bool {
pub fn contains(&self, pos: Pos2<T>) -> bool {
self.lower_left.x() >= pos.x()
&& pos.x() >= self.upper_right.x()
&& self.lower_left.y() >= pos.y()
@ -62,11 +62,11 @@ where
{
pub fn from_iterator<I>(mut iter: I) -> Option<Self>
where
I: Iterator<Item = &'a Pos<T>>,
I: Iterator<Item = &'a Pos2<T>>,
{
let first = *iter.next()?;
let (upper, lower) = iter.fold((first, first), |(mx, mn), p| {
(mx.max_components(p), mn.min_components(p))
(mx.max_components(*p), mn.min_components(*p))
});
Some(Area::new(lower, upper))
@ -214,12 +214,12 @@ impl<'a, T> Iterator for ColIterator<'a, T>
where
T: Num + Ord + NumAssignOps + Copy,
{
type Item = Pos<T>;
type Item = Pos2<T>;
fn next(&mut self) -> Option<Self::Item> {
if (self.ascending && self.col <= self.area.upper_right.x())
|| (!self.ascending && self.col >= self.area.lower_left.x())
{
let pos = Pos::new(self.col, self.row);
let pos = Pos2::new(self.col, self.row);
if self.ascending {
self.col += T::one();
} else {
@ -266,13 +266,13 @@ impl<'a, T> Iterator for CellIterator<'a, T>
where
T: Num + Ord + NumAssignOps + Copy,
{
type Item = Pos<T>;
type Item = Pos2<T>;
fn next(&mut self) -> Option<Self::Item> {
if (self.ascending && self.row <= self.area.upper_right.y())
|| (!self.ascending && self.row >= self.area.lower_left.y())
{
let pos = Pos::new(self.col, self.row);
let pos = Pos2::new(self.col, self.row);
if self.ascending {
self.col += T::one();
if self.col > self.area.upper_right.x() {
@ -300,18 +300,18 @@ mod test {
#[test]
fn test_cell_iterator() {
let area = Area::new(Pos::new(-1, -1), Pos::new(1, 1));
let area = Area::new(Pos2::new(-1, -1), Pos2::new(1, 1));
let result = area.cells(true).collect::<Vec<_>>();
let expected = vec![
Pos::new(-1, -1),
Pos::new(0, -1),
Pos::new(1, -1),
Pos::new(-1, 0),
Pos::new(0, 0),
Pos::new(1, 0),
Pos::new(-1, 1),
Pos::new(0, 1),
Pos::new(1, 1),
Pos2::new(-1, -1),
Pos2::new(0, -1),
Pos2::new(1, -1),
Pos2::new(-1, 0),
Pos2::new(0, 0),
Pos2::new(1, 0),
Pos2::new(-1, 1),
Pos2::new(0, 1),
Pos2::new(1, 1),
];
assert_eq!(result, expected);
}

View file

@ -1,27 +1,18 @@
use super::{pos::Pos, turn::Turn};
use super::{pos2::Pos2, turn::Turn};
use num_traits::{Num, Signed};
use std::{fmt::Display, ops::Add};
use Direction::*;
use Turn::*;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Direction {
East,
North,
West,
South,
East = 0,
North = 1,
West = 2,
South = 3,
}
impl Direction {
pub fn as_pos(&self) -> Pos<i32> {
match *self {
East => Pos::new(1, 0),
North => Pos::new(0, 1),
West => Pos::new(-1, 0),
South => Pos::new(0, -1),
}
}
pub fn is_perpendicular(&self, other: &Direction) -> bool {
match *self {
East => *other != East && *other != West,
@ -91,35 +82,26 @@ impl Display for Direction {
}
}
impl Add<Pos<i32>> for Direction {
type Output = Pos<i32>;
fn add(self, rhs: Pos<i32>) -> Self::Output {
Pos::add(rhs, self)
impl<T> From<Direction> for Pos2<T>
where
T: Num + Signed,
{
fn from(value: Direction) -> Self {
match value {
East => Pos2::new(T::one(), T::zero()),
North => Pos2::new(T::zero(), -T::one()),
West => Pos2::new(-T::one(), T::zero()),
South => Pos2::new(T::zero(), T::one()),
}
}
}
impl Add<Pos<i32>> for &Direction {
type Output = Pos<i32>;
impl Add<Direction> for Pos2<i32> {
type Output = Pos2<i32>;
fn add(self, rhs: Pos<i32>) -> Self::Output {
Pos::add(rhs, *self)
}
}
impl Add<&Pos<i32>> for Direction {
type Output = Pos<i32>;
fn add(self, rhs: &Pos<i32>) -> Self::Output {
Pos::add(*rhs, self)
}
}
impl Add<&Pos<i32>> for &Direction {
type Output = Pos<i32>;
fn add(self, rhs: &Pos<i32>) -> Self::Output {
Pos::add(*rhs, *self)
fn add(self, rhs: Direction) -> Self::Output {
let rhs: Pos2<i32> = rhs.into();
self + rhs
}
}
@ -130,27 +112,3 @@ impl Add<Turn> for Direction {
self.turn(rhs)
}
}
impl Add<&Turn> for Direction {
type Output = Self;
fn add(self, rhs: &Turn) -> Direction {
Direction::add(self, *rhs)
}
}
impl Add<Turn> for &Direction {
type Output = Direction;
fn add(self, rhs: Turn) -> Direction {
Direction::add(*self, rhs)
}
}
impl Add<&Turn> for &Direction {
type Output = Direction;
fn add(self, rhs: &Turn) -> Direction {
Direction::add(*self, *rhs)
}
}

View file

@ -27,20 +27,24 @@ where
a
}
pub fn gcd<T>(a: T, b: T) -> T
pub fn gcd<T>(a: T, b: T) -> Option<T>
where
T: Num + Ord + Copy,
{
assert!(a >= T::zero());
assert!(b >= T::zero());
if a.is_zero() {
b
} else if b.is_zero() {
a
if b.is_zero() {
None
} else {
Some(b)
}
} else {
non_zero_gcd(a, b)
Some(non_zero_gcd(a, b))
}
}
pub fn lcm<T>(a: T, b: T) -> T
where
T: Num + Ord + Copy,
@ -77,9 +81,9 @@ mod tests {
#[test]
fn some_simple_gcd() {
assert_eq!(5, gcd(10, 15));
assert_eq!(7, gcd(21, 49));
assert_eq!(1, gcd(13, 17));
assert_eq!(Some(5), gcd(10, 15));
assert_eq!(Some(7), gcd(21, 49));
assert_eq!(Some(1), gcd(13, 17));
}
#[test]

View file

@ -1,8 +1,12 @@
pub mod abs;
pub mod area;
pub mod direction;
pub mod file;
pub mod helper;
pub mod math;
pub mod name;
pub mod parser;
pub mod pos;
pub mod pos2;
pub mod pos3;
pub mod turn;
pub mod unit_vector;

30
src/common/name.rs Normal file
View file

@ -0,0 +1,30 @@
use std::{fmt::Display, ops::Deref, rc::Rc};
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Name(Rc<str>);
impl Name {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Deref for Name {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<&str> for Name {
fn from(value: &str) -> Self {
Self(Rc::from(value))
}
}
impl Display for Name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

View file

@ -1,7 +1,7 @@
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{line_ending, space0, space1},
character::complete::{line_ending, space0, space1, u32},
combinator::{eof, opt, value},
error::ParseError,
multi::many0,
@ -34,6 +34,10 @@ where
terminated(line, alt((line_ending, eof)))
}
pub fn usize<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, usize, E> {
u32.map(|v| v as usize).parse(input)
}
pub fn true_false<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, bool, E> {
alt((value(true, tag("true")), value(false, tag("false"))))(input)
}

View file

@ -1,278 +0,0 @@
use super::direction::Direction;
use super::math::gcd;
use num_traits::{Num, NumCast, Signed};
use std::fmt;
use std::ops::{Add, Mul, Sub};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Pos<T>(T, T)
where
T: Num;
impl<T> Pos<T>
where
T: Num,
{
pub fn new(x: T, y: T) -> Pos<T> {
Pos(x, y)
}
}
impl<T> Pos<T>
where
T: Num + Copy,
{
pub fn splat(v: T) -> Pos<T> {
Pos(v, v)
}
pub fn x(&self) -> T {
self.0
}
pub fn y(&self) -> T {
self.1
}
}
impl<T> Pos<T>
where
T: Num + Ord + Copy,
{
pub fn normalize(&self) -> (Pos<T>, T) {
if self.0.is_zero() && self.1.is_zero() {
(*self, T::one())
} else {
let ggt = gcd(self.0, self.1);
(Pos::new(self.0 / ggt, self.1 / ggt), ggt)
}
}
}
impl<T> Pos<T>
where
T: Num + NumCast,
{
pub fn angle(&self) -> f64 {
if let (Some(x), Some(y)) = (self.0.to_f64(), self.1.to_f64()) {
y.atan2(x)
} else {
f64::NAN
}
}
pub fn angle2(&self) -> f64 {
if let (Some(x), Some(y)) = (self.0.to_f64(), self.1.to_f64()) {
(-x.atan2(-y) + std::f64::consts::PI).rem_euclid(2.0 * std::f64::consts::PI)
} else {
f64::NAN
}
}
}
impl<T> Pos<T>
where
T: Num + Ord + Copy,
{
pub fn max_components(&self, other: &Pos<T>) -> Self {
Self(self.0.max(other.0), self.1.max(other.1))
}
pub fn min_components(&self, other: &Pos<T>) -> Self {
Self(self.0.min(other.0), self.1.min(other.1))
}
}
impl<T> Pos<T>
where
T: Num + Signed,
{
pub fn abs(&self) -> T {
self.0.abs() + self.1.abs()
}
}
impl<T> fmt::Display for Pos<T>
where
T: Num + fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.0, self.1)
}
}
impl<T> Add for Pos<T>
where
T: Num,
{
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Pos(self.0 + rhs.0, self.1 + rhs.1)
}
}
impl<T> Add for &Pos<T>
where
T: Num + Copy,
{
type Output = <Pos<T> as Add<Pos<T>>>::Output;
fn add(self, rhs: Self) -> Self::Output {
Pos::add(*self, *rhs)
}
}
impl<T> Add<&Pos<T>> for Pos<T>
where
T: Num + Copy,
{
type Output = <Pos<T> as Add<Pos<T>>>::Output;
fn add(self, rhs: &Self) -> Self::Output {
Pos::add(self, *rhs)
}
}
impl<T> Add<Pos<T>> for &Pos<T>
where
T: Num + Copy,
{
type Output = <Pos<T> as Add<Pos<T>>>::Output;
fn add(self, rhs: Pos<T>) -> Self::Output {
Pos::add(*self, rhs)
}
}
impl<T> Add<(T, T)> for Pos<T>
where
T: Num,
{
type Output = Self;
fn add(self, rhs: (T, T)) -> Self::Output {
Pos(self.0 + rhs.0, self.1 + rhs.1)
}
}
impl Add<Direction> for Pos<i32> {
type Output = Self;
fn add(self, rhs: Direction) -> Self::Output {
Pos::add(self, rhs.as_pos())
}
}
impl Add<&Direction> for Pos<i32> {
type Output = Self;
fn add(self, rhs: &Direction) -> Self::Output {
Pos::add(self, rhs.as_pos())
}
}
impl Add<&Direction> for &Pos<i32> {
type Output = Pos<i32>;
fn add(self, rhs: &Direction) -> Self::Output {
Pos::add(*self, rhs.as_pos())
}
}
impl Add<Direction> for &Pos<i32> {
type Output = Pos<i32>;
fn add(self, rhs: Direction) -> Self::Output {
Pos::add(*self, rhs.as_pos())
}
}
impl<T> Sub for Pos<T>
where
T: Num,
{
type Output = Pos<T>;
fn sub(self, rhs: Self) -> Self::Output {
Pos(self.0 - rhs.0, self.1 - rhs.1)
}
}
impl<T> Sub<&Self> for Pos<T>
where
T: Num + Copy,
{
type Output = Pos<T>;
fn sub(self, rhs: &Self) -> Self::Output {
Pos::sub(self, *rhs)
}
}
impl<T> Sub for &Pos<T>
where
T: Num + Copy,
{
type Output = Pos<T>;
fn sub(self, rhs: &Pos<T>) -> Self::Output {
Pos::sub(*self, *rhs)
}
}
impl<T> Sub<Pos<T>> for &Pos<T>
where
T: Num + Copy,
{
type Output = Pos<T>;
fn sub(self, rhs: Pos<T>) -> Self::Output {
Pos::sub(*self, rhs)
}
}
impl<T> Mul<T> for Pos<T>
where
T: Num + Copy,
{
type Output = Self;
fn mul(self, rhs: T) -> Self::Output {
Pos(self.0 * rhs, self.1 * rhs)
}
}
impl<T> Mul<T> for &Pos<T>
where
T: Num + Copy,
{
type Output = Pos<T>;
fn mul(self, rhs: T) -> Self::Output {
Pos::mul(*self, rhs)
}
}
impl<T> Mul<&T> for Pos<T>
where
T: Num + Copy,
{
type Output = Pos<T>;
fn mul(self, rhs: &T) -> Self::Output {
Pos::mul(self, *rhs)
}
}
impl<T> Mul<&T> for &Pos<T>
where
T: Num + Copy,
{
type Output = Pos<T>;
fn mul(self, rhs: &T) -> Self::Output {
Pos::mul(*self, *rhs)
}
}
impl<T> Pos<T>
where
T: Num + Signed + Copy,
{
pub fn taxicab_origin(&self) -> T {
self.0.abs() + self.1.abs()
}
pub fn taxicab(&self, other: &Pos<T>) -> T {
(self.0 - other.0).abs() + (self.1 - other.1).abs()
}
}

269
src/common/pos2.rs Normal file
View file

@ -0,0 +1,269 @@
#![allow(dead_code)]
use super::direction::Direction;
use super::{abs::Abs, math::gcd};
use num_traits::{CheckedAdd, CheckedSub, Float, Num, NumCast, Signed, Zero};
use std::fmt;
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Pos2<T>([T; 2]);
impl<T> Pos2<T> {
#[inline]
pub fn new(x: T, y: T) -> Pos2<T> {
Pos2([x, y])
}
#[inline]
pub fn get_x(&self) -> &T {
&self.0[0]
}
#[inline]
pub fn get_y(&self) -> &T {
&self.0[1]
}
}
impl<T> From<[T; 2]> for Pos2<T> {
fn from(value: [T; 2]) -> Self {
Pos2(value)
}
}
impl<T> From<(T, T)> for Pos2<T> {
fn from(value: (T, T)) -> Self {
Pos2([value.0, value.1])
}
}
impl<T> Pos2<T>
where
T: Copy,
{
#[inline]
pub fn splat(v: T) -> Pos2<T> {
Pos2::new(v, v)
}
pub fn x(&self) -> T {
self.0[0]
}
pub fn y(&self) -> T {
self.0[1]
}
}
impl<T> Pos2<T>
where
T: Num + Ord + Copy,
{
pub fn normalize(self) -> Result<(Pos2<T>, T), Pos2<T>> {
if self.x().is_zero() && self.y().is_zero() {
Err(self)
} else {
gcd(self.x(), self.y())
.map(|ggt| (Pos2::new(self.x() / ggt, self.y() / ggt), ggt))
.ok_or(self)
}
}
}
impl<T> Pos2<T>
where
T: Float,
{
pub fn normal(self) -> Result<(Pos2<T>, T), Pos2<T>> {
let length = self.length();
if length == T::zero() {
Err(self)
} else {
Ok((self / length, length))
}
}
}
impl<T> Pos2<T>
where
T: Num + NumCast,
{
pub fn angle(&self) -> Option<f64> {
if let (Some(x), Some(y)) = (self.get_x().to_f64(), self.get_y().to_f64()) {
Some(y.atan2(x))
} else {
None
}
}
pub fn angle2(&self) -> Option<f64> {
if let (Some(x), Some(y)) = (self.get_x().to_f64(), self.get_y().to_f64()) {
Some((-x.atan2(-y) + std::f64::consts::PI).rem_euclid(2.0 * std::f64::consts::PI))
} else {
None
}
}
}
impl<T> Pos2<T>
where
T: Ord + Copy,
{
pub fn max_components(self, other: Pos2<T>) -> Self {
Pos2::new(self.x().max(other.x()), self.y().max(other.y()))
}
pub fn min_components(self, other: Pos2<T>) -> Self {
Pos2::new(self.x().min(other.x()), self.y().min(other.y()))
}
}
impl<T> Pos2<T>
where
T: Num + Abs,
{
pub fn abs(self) -> T {
self.get_x().abs() + self.get_y().abs()
}
}
impl<T> Pos2<T>
where
T: Float,
{
pub fn length(self) -> T {
(self.get_x().powi(2) + self.get_y().powi(2)).sqrt()
}
}
impl<T> fmt::Display for Pos2<T>
where
T: Num + fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.get_x(), self.get_y())
}
}
impl<T> Zero for Pos2<T>
where
T: Num + Zero + Copy,
{
fn zero() -> Self {
Pos2::splat(T::zero())
}
fn is_zero(&self) -> bool {
self.x().is_zero() && self.y().is_zero()
}
}
impl<T> Add for Pos2<T>
where
T: Num + Copy,
{
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Pos2::new(self.x() + rhs.x(), self.y() + rhs.y())
}
}
impl<T> Add<(T, T)> for Pos2<T>
where
T: Num + Copy,
{
type Output = Self;
fn add(self, rhs: (T, T)) -> Self::Output {
Pos2::new(self.x() + rhs.0, self.y() + rhs.1)
}
}
impl<T, P: Into<Pos2<T>>> AddAssign<P> for Pos2<T>
where
T: AddAssign<T> + Copy,
{
fn add_assign(&mut self, rhs: P) {
let rhs = rhs.into();
self.0[0] += rhs.0[0];
self.0[1] += rhs.0[1];
}
}
impl<T, P: Into<Pos2<T>>> Sub<P> for Pos2<T>
where
T: Num + Copy,
{
type Output = Pos2<T>;
fn sub(self, rhs: P) -> Self::Output {
let rhs = rhs.into();
Pos2::new(self.x() - rhs.x(), self.y() - rhs.y())
}
}
impl<T> Mul<T> for Pos2<T>
where
T: Num + Copy,
{
type Output = Self;
fn mul(self, rhs: T) -> Self::Output {
Pos2::new(self.x() * rhs, self.y() * rhs)
}
}
impl<T> Div<T> for Pos2<T>
where
T: Num + Copy,
{
type Output = Self;
fn div(self, rhs: T) -> Self::Output {
Pos2::new(self.x() / rhs, self.y() / rhs)
}
}
impl<T> Neg for Pos2<T>
where
T: Signed + Copy,
{
type Output = Pos2<T>;
fn neg(self) -> Self::Output {
Self([-self.x(), -self.y()])
}
}
impl<T> Pos2<T>
where
T: Num + Abs + Copy,
{
pub fn taxicab_between(self, other: Pos2<T>) -> T {
self.x().abs_beween(&other.x()) + self.y().abs_beween(&other.y())
}
}
impl<T> Pos2<T>
where
T: Num + Copy + CheckedAdd + CheckedSub,
{
pub fn check_add(self, direction: Direction) -> Option<Self> {
match direction {
Direction::East => self
.x()
.checked_add(&T::one())
.map(|x| Pos2::new(x, self.y())),
Direction::North => self
.y()
.checked_sub(&T::one())
.map(|y| Pos2::new(self.x(), y)),
Direction::West => self
.x()
.checked_sub(&T::one())
.map(|x| Pos2::new(x, self.y())),
Direction::South => self
.y()
.checked_add(&T::one())
.map(|y| Pos2::new(self.x(), y)),
}
}
}

182
src/common/pos3.rs Normal file
View file

@ -0,0 +1,182 @@
#![allow(dead_code)]
use num_traits::{Num, PrimInt, Signed, Zero};
use std::fmt;
use std::ops::{Add, Div, Mul, Neg, Sub};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Pos3<T>([T; 3]);
impl<T> Pos3<T> {
#[inline]
pub const fn new(x: T, y: T, z: T) -> Pos3<T> {
Pos3([x, y, z])
}
#[inline]
pub fn get_x(&self) -> &T {
&self.0[0]
}
#[inline]
pub fn get_y(&self) -> &T {
&self.0[1]
}
#[inline]
pub fn get_z(&self) -> &T {
&self.0[2]
}
}
impl<T: Signed + PrimInt> Pos3<T> {
pub fn is_unit(&self) -> bool {
self.abs() == T::one()
}
}
impl<T: Copy + Default> From<&[T]> for Pos3<T> {
fn from(value: &[T]) -> Self {
match value.len() {
0 => Pos3::new(T::default(), T::default(), T::default()),
1 => Pos3::new(value[0], T::default(), T::default()),
2 => Pos3::new(value[0], value[1], T::default()),
_ => Pos3::new(value[0], value[1], value[2]),
}
}
}
impl<T> From<[T; 3]> for Pos3<T> {
fn from(value: [T; 3]) -> Self {
Pos3(value)
}
}
impl<T> From<(T, T, T)> for Pos3<T> {
fn from(value: (T, T, T)) -> Self {
Pos3([value.0, value.1, value.2])
}
}
impl<T> Pos3<T>
where
T: Copy,
{
#[inline]
pub fn splat(v: T) -> Pos3<T> {
Pos3::new(v, v, v)
}
#[inline]
pub fn x(&self) -> T {
self.0[0]
}
#[inline]
pub fn y(&self) -> T {
self.0[1]
}
#[inline]
pub fn z(&self) -> T {
self.0[2]
}
}
impl<T> Zero for Pos3<T>
where
T: Num + Zero + Copy,
{
fn zero() -> Self {
Pos3::splat(T::zero())
}
fn is_zero(&self) -> bool {
self.x().is_zero() && self.y().is_zero() && self.z().is_zero()
}
}
impl<T> Pos3<T>
where
T: Signed,
{
pub fn abs(self) -> T {
self.get_x().abs() + self.get_y().abs() + self.get_z().abs()
}
}
impl<T> fmt::Display for Pos3<T>
where
T: Num + fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {}, {})", self.get_x(), self.get_y(), self.get_z())
}
}
impl<T, P: Into<Pos3<T>>> Add<P> for Pos3<T>
where
T: Num + Copy,
{
type Output = Self;
fn add(self, rhs: P) -> Self::Output {
let rhs = rhs.into();
Pos3::new(self.x() + rhs.x(), self.y() + rhs.y(), self.z() + rhs.z())
}
}
impl<T, P: Into<Pos3<T>>> Sub<P> for Pos3<T>
where
T: Num + Copy,
{
type Output = Pos3<T>;
fn sub(self, rhs: P) -> Self::Output {
let rhs = rhs.into();
Pos3::new(self.x() - rhs.x(), self.y() - rhs.y(), self.z() - rhs.z())
}
}
impl<T> Mul<T> for Pos3<T>
where
T: Num + Copy,
{
type Output = Self;
fn mul(self, rhs: T) -> Self::Output {
Pos3::new(self.x() * rhs, self.y() * rhs, self.z() * rhs)
}
}
impl<T> Div<T> for Pos3<T>
where
T: Num + Copy,
{
type Output = Self;
fn div(self, rhs: T) -> Self::Output {
Pos3::new(self.x() / rhs, self.y() / rhs, self.z() / rhs)
}
}
impl<T> Neg for Pos3<T>
where
T: Signed + Copy,
{
type Output = Pos3<T>;
fn neg(self) -> Self::Output {
Pos3::new(-self.x(), -self.y(), -self.z())
}
}
impl<T> Mul<Pos3<T>> for Pos3<T>
where
T: Num + Copy,
{
type Output = Pos3<T>;
fn mul(self, rhs: Pos3<T>) -> Self::Output {
Pos3([
self.y() * rhs.z() - self.z() * rhs.y(),
self.z() * rhs.x() - self.x() * rhs.z(),
self.x() * rhs.y() - self.y() * rhs.x(),
])
}
}

53
src/common/unit_vector.rs Normal file
View file

@ -0,0 +1,53 @@
#![allow(dead_code)]
use super::pos3::Pos3;
use std::ops::{Mul, Neg};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UnitVector(Pos3<i8>);
pub const X: UnitVector = UnitVector(Pos3::new(1, 0, 0));
pub const NEG_X: UnitVector = UnitVector(Pos3::new(-1, 0, 0));
pub const Y: UnitVector = UnitVector(Pos3::new(0, 1, 0));
pub const NEG_Y: UnitVector = UnitVector(Pos3::new(0, -1, 0));
pub const Z: UnitVector = UnitVector(Pos3::new(0, 0, 1));
pub const NEG_Z: UnitVector = UnitVector(Pos3::new(0, 0, -1));
impl UnitVector {
pub fn new(vector: Pos3<i8>) -> Option<Self> {
vector.is_unit().then(|| UnitVector(vector))
}
pub fn x(self) -> i8 {
self.0.x()
}
pub fn y(self) -> i8 {
self.0.y()
}
pub fn z(self) -> i8 {
self.0.z()
}
}
impl From<UnitVector> for Pos3<i8> {
fn from(value: UnitVector) -> Self {
value.0
}
}
impl Mul for UnitVector {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
UnitVector(self.0 * rhs.0)
}
}
impl Neg for UnitVector {
type Output = Self;
fn neg(self) -> Self::Output {
UnitVector(-self.0)
}
}

View file

@ -1,12 +1,9 @@
use super::template::{DayTrait, ResultType};
use crate::common::file::split_lines;
use itertools::Itertools;
use std::num::ParseIntError;
use thiserror::Error;
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
pub struct Day;
const DAY_NUMBER: usize = 1;

View file

@ -1,8 +1,6 @@
use std::cmp::Ordering;
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
use crate::common::file::split_lines;
use std::{cmp::Ordering, str::FromStr};
use thiserror::Error;
const DAY_NUMBER: usize = 2;
@ -60,9 +58,7 @@ impl Rps {
Err(RPSError::IllegalLine(line.to_owned()))?
};
let first = Rps::try_from(first)?;
let second = Rps::try_from(second)?;
Ok((first, second))
Ok((first.parse()?, second.parse()?))
}
pub fn value(&self) -> i64 {
@ -98,10 +94,10 @@ impl PartialOrd for Rps {
}
}
impl TryFrom<&str> for Rps {
type Error = RPSError;
impl FromStr for Rps {
type Err = RPSError;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"A" | "X" => Ok(Rps::Rock),
"B" | "Y" => Ok(Rps::Paper),
@ -110,6 +106,7 @@ impl TryFrom<&str> for Rps {
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
enum Strategy {
Loose,
@ -124,9 +121,7 @@ impl Strategy {
Err(RPSError::IllegalLine(line.to_owned()))?
};
let first = Rps::try_from(first)?;
let second = Strategy::try_from(second)?;
Ok((first, second))
Ok((first.parse()?, second.parse()?))
}
pub fn fullfill(&self, other: &Rps) -> Rps {
@ -139,10 +134,10 @@ impl Strategy {
}
}
impl TryFrom<&str> for Strategy {
type Error = RPSError;
impl FromStr for Strategy {
type Err = RPSError;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"X" => Ok(Strategy::Loose),
"Y" => Ok(Strategy::Draw),

View file

@ -1,6 +1,5 @@
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
use crate::common::file::split_lines;
use itertools::Itertools;
use thiserror::Error;

View file

@ -1,6 +1,5 @@
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
use crate::common::file::split_lines;
use itertools::Itertools;
use std::num::ParseIntError;
use thiserror::Error;

View file

@ -1,6 +1,5 @@
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
use crate::common::file::split_lines;
use itertools::Itertools;
use std::num::ParseIntError;
use thiserror::Error;
@ -124,7 +123,7 @@ impl Move {
if self.to_stack >= stacks.len() {
return Err(CrateError::TooFewStacks(stacks.len(), self.to_stack));
}
let Some(from_stack) = stacks.get(self.from_stack ) else {
let Some(from_stack) = stacks.get(self.from_stack) else {
return Err(CrateError::TooFewStacks(stacks.len(), self.from_stack));
};
if from_stack.len() < self.amount {

View file

@ -1,12 +1,10 @@
use super::template::{DayTrait, ResultType};
use crate::common::{file::split_lines, name::Name};
use std::{
cell::{Cell, RefCell},
num::ParseIntError,
rc::{Rc, Weak},
};
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
use thiserror::Error;
const DAY_NUMBER: usize = 7;
@ -75,7 +73,7 @@ enum DirectoryError {
#[derive(Debug)]
struct Node {
parent: Option<Weak<RefCell<Node>>>,
name: String,
name: Name,
sub_dirs: Vec<Rc<RefCell<Node>>>,
file_size: i64,
size: Cell<Option<i64>>,
@ -85,7 +83,7 @@ impl Node {
fn root() -> Node {
Node {
parent: None,
name: "/".to_owned(),
name: "/".into(),
sub_dirs: vec![],
file_size: 0,
size: Cell::new(None),
@ -96,7 +94,7 @@ impl Node {
self.sub_dirs.push(subdir);
}
fn add_file(&mut self, _name: &str, size: i64) {
fn add_file<N: Into<Name>>(&mut self, _name: N, size: i64) {
self.file_size += size;
}
@ -141,10 +139,10 @@ impl Directory {
self.node.borrow().file_size
}
pub fn add_subdir(&mut self, name: &str) {
pub fn add_subdir<N: Into<Name>>(&mut self, name: N) {
let subdir = Rc::new(RefCell::new(Node {
parent: Some(Rc::downgrade(&self.node)),
name: name.to_owned(),
name: name.into(),
sub_dirs: vec![],
file_size: 0,
size: Cell::new(None),
@ -152,7 +150,7 @@ impl Directory {
self.node.borrow_mut().add_subdir(subdir);
}
pub fn add_file(&mut self, name: &str, size: i64) {
pub fn add_file<N: Into<Name>>(&mut self, name: N, size: i64) {
self.node.borrow_mut().add_file(name, size)
}
@ -165,8 +163,9 @@ impl Directory {
.map(|node| Directory { node })
}
pub fn get_subdir(&self, name: &str) -> Option<Directory> {
pub fn get_subdir<N: Into<Name>>(&self, name: N) -> Option<Directory> {
let node = self.node.borrow();
let name = name.into();
let sub_node = node.sub_dirs.iter().find(|node| node.borrow().name == name);
sub_node.map(|node| Directory::create(node.clone()))
}
@ -181,14 +180,14 @@ impl Directory {
"/" => root.clone(),
".." => {
let Some(next) = current.parent() else {
return Err(DirectoryError::NoParentDirectory);
};
return Err(DirectoryError::NoParentDirectory);
};
next
}
_ => {
let Some(next) = current.get_subdir(dir) else {
return Err(DirectoryError::NoSuchDirectory(dir.to_owned()))
};
return Err(DirectoryError::NoSuchDirectory(dir.to_owned()));
};
next
}
};

View file

@ -1,9 +1,7 @@
use std::str::FromStr;
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
use crate::common::file::split_lines;
use itertools::{iproduct, FoldWhile, Itertools};
use std::str::FromStr;
use thiserror::Error;
const DAY_NUMBER: usize = 8;

View file

@ -1,5 +1,5 @@
use super::template::{DayTrait, ResultType};
use crate::common::{direction::Direction, file::split_lines, pos::Pos};
use crate::common::{direction::Direction, file::split_lines, pos2::Pos2};
use itertools::Itertools;
use std::{collections::HashSet, num::ParseIntError};
use thiserror::Error;
@ -63,27 +63,27 @@ fn parse_line(line: &str) -> Result<(Direction, usize), RopeError> {
}
}
fn get_closer(first: Pos<i32>, second: Pos<i32>) -> Option<Pos<i32>> {
fn get_closer(first: Pos2<i32>, second: Pos2<i32>) -> Option<Pos2<i32>> {
let diff = second - first;
if diff.x().abs() <= 1 && diff.y().abs() <= 1 {
None
} else {
Some(Pos::new(diff.x().signum(), diff.y().signum()))
Some(Pos2::new(diff.x().signum(), diff.y().signum()))
}
}
#[derive(Debug)]
struct Rope {
head: Pos<i32>,
knots: Vec<Pos<i32>>,
head: Pos2<i32>,
knots: Vec<Pos2<i32>>,
}
impl Rope {
pub fn create(length: usize) -> Self {
assert!(length >= 2);
Rope {
head: Pos::new(0, 0),
knots: vec![Pos::new(0, 0); length - 1],
head: Pos2::new(0, 0),
knots: vec![Pos2::new(0, 0); length - 1],
}
}

View file

@ -1,7 +1,7 @@
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
use std::{num::ParseIntError, slice::Iter};
use crate::common::file::split_lines;
use itertools::Itertools;
use std::{num::ParseIntError, slice::Iter, str::FromStr};
use thiserror::Error;
const DAY_NUMBER: usize = 10;
@ -14,17 +14,13 @@ impl DayTrait for Day {
}
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
let instructions = split_lines(lines)
.map(|line| Instruction::parse(line))
.collect::<Result<Vec<_>, _>>()?;
let instructions: Vec<_> = split_lines(lines).map(|line| line.parse()).try_collect()?;
let result = CpuCycles::signal_strength(&instructions, &[20, 60, 100, 140, 180, 220]);
Ok(ResultType::Integer(result as i64))
}
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
let instructions = split_lines(lines)
.map(|line| Instruction::parse(line))
.collect::<Result<Vec<_>, _>>()?;
let instructions: Vec<_> = split_lines(lines).map(|line| line.parse()).try_collect()?;
let result = CpuCycles::draw(&instructions);
Ok(ResultType::Lines(result))
}
@ -39,18 +35,18 @@ enum CpuError {
InvalidInteger(#[from] ParseIntError),
}
enum Instruction {
Add(i32),
Noop,
}
#[derive(Debug, PartialEq, Eq)]
struct Instruction(Option<i32>);
impl Instruction {
pub fn parse(line: &str) -> Result<Self, CpuError> {
impl FromStr for Instruction {
type Err = CpuError;
fn from_str(line: &str) -> Result<Self, Self::Err> {
if line == "noop" {
Ok(Instruction::Noop)
Ok(Instruction(None))
} else if line.starts_with("addx") {
let value = line[5..].parse()?;
Ok(Instruction::Add(value))
Ok(Instruction(Some(value)))
} else {
Err(CpuError::UnknownInstruction(line.to_owned()))
}
@ -114,10 +110,7 @@ impl<'a> Iterator for CpuCycles<'a> {
self.current = None;
Some(start_register)
} else if let Some(instruction) = self.instructions.next() {
match instruction {
Instruction::Add(value) => self.current = Some(*value),
Instruction::Noop => {}
}
self.current = instruction.0;
Some(self.register)
} else {
None
@ -134,7 +127,11 @@ mod test {
#[test]
fn test_simple() -> Result<()> {
let instructions = vec![Instruction::Noop, Instruction::Add(3), Instruction::Add(-5)];
let instructions = vec![
Instruction(None),
Instruction(Some(3)),
Instruction(Some(-5)),
];
let expected = vec![1, 1, 1, 4, 4];
let result = CpuCycles::create(&instructions).collect::<Vec<_>>();
assert_eq!(result, expected);

View file

@ -1,8 +1,7 @@
use super::template::{DayTrait, ResultType};
use crate::common::parser::{
empty_lines, eol_terminated, extract_result, ignore, trim0, trim_left1, true_false,
};
use super::template::{DayTrait, ResultType};
use nom::{
branch::alt,
bytes::complete::tag,

View file

@ -1,8 +1,6 @@
use std::collections::{BinaryHeap, HashSet};
use crate::common::{file::split_lines, pos::Pos};
use super::template::{DayTrait, ResultType};
use crate::common::{file::split_lines, pos2::Pos2};
use std::collections::{BinaryHeap, HashSet};
use thiserror::Error;
const DAY_NUMBER: usize = 12;
@ -45,7 +43,7 @@ enum ValleyError {
struct Path {
length: usize,
height: char,
pos: Pos<usize>,
pos: Pos2<usize>,
}
impl PartialOrd for Path {
@ -78,7 +76,7 @@ impl Ord for Path {
}
impl Path {
pub fn new(length: usize, height: char, pos: Pos<usize>) -> Self {
pub fn new(length: usize, height: char, pos: Pos2<usize>) -> Self {
Path {
length,
height,
@ -106,28 +104,28 @@ impl<'a> Neighbors<'a> {
}
}
fn next_pos(&mut self) -> Option<Pos<usize>> {
fn next_pos(&mut self) -> Option<Pos2<usize>> {
while self.state < 4 {
self.state += 1;
match self.state {
1 => {
if self.path.pos.x() < self.valley.width() - 1 {
return Some(Pos::new(self.path.pos.x() + 1, self.path.pos.y()));
return Some(Pos2::new(self.path.pos.x() + 1, self.path.pos.y()));
}
}
2 => {
if self.path.pos.y() > 0 {
return Some(Pos::new(self.path.pos.x(), self.path.pos.y() - 1));
return Some(Pos2::new(self.path.pos.x(), self.path.pos.y() - 1));
}
}
3 => {
if self.path.pos.x() > 0 {
return Some(Pos::new(self.path.pos.x() - 1, self.path.pos.y()));
return Some(Pos2::new(self.path.pos.x() - 1, self.path.pos.y()));
}
}
4 => {
if self.path.pos.y() < self.valley.height() - 1 {
return Some(Pos::new(self.path.pos.x(), self.path.pos.y() + 1));
return Some(Pos2::new(self.path.pos.x(), self.path.pos.y() + 1));
}
}
_ => {}
@ -153,8 +151,8 @@ impl Iterator for Neighbors<'_> {
struct Valley {
map: Vec<Vec<char>>,
start: Pos<usize>,
exit: Pos<usize>,
start: Pos2<usize>,
exit: Pos2<usize>,
width: usize,
}
@ -172,11 +170,11 @@ impl TryFrom<&str> for Valley {
for (x, height_char) in row.chars().enumerate() {
match height_char {
'S' => {
start = Some(Pos::new(x, y));
start = Some(Pos2::new(x, y));
height_row.push('a')
}
'E' => {
exit = Some(Pos::new(x, y));
exit = Some(Pos2::new(x, y));
height_row.push('z')
}
'a'..='z' => height_row.push(height_char),
@ -211,13 +209,13 @@ impl TryFrom<&str> for Valley {
}
impl Valley {
fn get_height(&self, pos: Pos<usize>) -> char {
fn get_height(&self, pos: Pos2<usize>) -> char {
self.map[pos.y()][pos.x()]
}
fn do_walk<F>(&self, check: F) -> Result<usize, ValleyError>
where
F: Fn(Pos<usize>) -> bool,
F: Fn(Pos2<usize>) -> bool,
{
let mut shortest = HashSet::with_capacity(self.width * self.map.len());
let mut queue = BinaryHeap::new();
@ -269,8 +267,8 @@ mod test {
let lines = read_string(day.get_day_number(), "example01.txt")?;
let valley = Valley::try_from(lines.as_str())?;
assert_eq!(valley.width, 8);
assert_eq!(valley.start, Pos::new(0, 0));
assert_eq!(valley.exit, Pos::new(5, 2));
assert_eq!(valley.start, Pos2::new(0, 0));
assert_eq!(valley.exit, Pos2::new(5, 2));
Ok(())
}

View file

@ -1,9 +1,7 @@
use std::cmp::Ordering;
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
use crate::common::file::split_lines;
use itertools::Itertools;
use std::cmp::Ordering;
use thiserror::Error;
const DAY_NUMBER: usize = 13;

View file

@ -1,11 +1,9 @@
use super::template::{DayTrait, ResultType};
use crate::common::{file::split_lines, pos2::Pos2};
use itertools::Itertools;
use std::{collections::HashSet, num::ParseIntError};
use thiserror::Error;
use crate::common::{file::split_lines, pos::Pos};
use super::template::{DayTrait, ResultType};
const DAY_NUMBER: usize = 14;
pub struct Day;
@ -56,7 +54,7 @@ impl TryFrom<&str> for Cave {
fn try_from(lines: &str) -> Result<Self, Self::Error> {
let lines = split_lines(lines);
let mut cave: HashSet<Pos<i32>> = HashSet::new();
let mut cave: HashSet<Pos2<i32>> = HashSet::new();
for line in lines {
cave.extend(Cave::parse_one(line)?.iter());
}
@ -92,7 +90,7 @@ impl TryFrom<&str> for Cave {
}
impl Cave {
fn get(&self, map: &[Vec<bool>], pos: Pos<i32>) -> bool {
fn get(&self, map: &[Vec<bool>], pos: Pos2<i32>) -> bool {
if pos.y() >= self.height || pos.x() < self.min_x || pos.x() > self.max_x {
false
} else {
@ -100,28 +98,28 @@ impl Cave {
}
}
fn set(&self, map: &mut [Vec<bool>], pos: Pos<i32>) {
fn set(&self, map: &mut [Vec<bool>], pos: Pos2<i32>) {
map[pos.y() as usize][(pos.x() - self.min_x) as usize] = true
}
fn walk(from: Pos<i32>, to: Pos<i32>) -> Result<Vec<Pos<i32>>, CaveError> {
fn walk(from: Pos2<i32>, to: Pos2<i32>) -> Result<Vec<Pos2<i32>>, CaveError> {
if from == to {
return Ok(vec![from]);
}
if from.x() == to.x() {
if from.y() < to.y() {
Ok((from.y()..to.y()).map(|y| Pos::new(from.x(), y)).collect())
Ok((from.y()..to.y()).map(|y| Pos2::new(from.x(), y)).collect())
} else {
Ok(((to.y() + 1)..=from.y())
.map(|y| Pos::new(from.x(), y))
.map(|y| Pos2::new(from.x(), y))
.collect())
}
} else if from.y() == to.y() {
if from.x() < to.x() {
Ok((from.x()..to.x()).map(|x| Pos::new(x, from.y())).collect())
Ok((from.x()..to.x()).map(|x| Pos2::new(x, from.y())).collect())
} else {
Ok(((to.x() + 1)..=from.x())
.map(|x| Pos::new(x, from.y()))
.map(|x| Pos2::new(x, from.y()))
.collect())
}
} else {
@ -129,14 +127,14 @@ impl Cave {
}
}
fn parse_one(line: &str) -> Result<HashSet<Pos<i32>>, CaveError> {
fn parse_one(line: &str) -> Result<HashSet<Pos2<i32>>, CaveError> {
let corners = line
.split("->")
.map(|coord| match coord.split(',').collect::<Vec<_>>()[..] {
[first, second] => {
let x: i32 = first.trim().parse()?;
let y = second.trim().parse()?;
Ok(Pos::new(x, y))
Ok(Pos2::new(x, y))
}
_ => Err(CaveError::NotAValidPos(coord.to_owned())),
})
@ -173,8 +171,8 @@ impl Cave {
drops
}
fn drop_one(&self, map: &[Vec<bool>]) -> Option<Pos<i32>> {
let mut drop = Pos::new(500, 0);
fn drop_one(&self, map: &[Vec<bool>]) -> Option<Pos2<i32>> {
let mut drop = Pos2::new(500, 0);
loop {
let next_y = drop.y() + 1;
if next_y > self.height {
@ -182,7 +180,7 @@ impl Cave {
}
let mut stuck = true;
for dx in [0, -1, 1] {
let next = Pos::new(drop.x() + dx, next_y);
let next = Pos2::new(drop.x() + dx, next_y);
if !self.get(map, next) {
drop = next;
stuck = false;
@ -254,7 +252,7 @@ mod test {
#[test]
fn test_parse() -> Result<()> {
let input = "498,4 -> 498,6 -> 496,6";
let expected = hashset! {Pos::new(498, 4), Pos::new(498, 5), Pos::new(498, 6), Pos::new(497, 6), Pos::new(496, 6)};
let expected = hashset! {Pos2::new(498, 4), Pos2::new(498, 5), Pos2::new(498, 6), Pos2::new(497, 6), Pos2::new(496, 6)};
let result = Cave::parse_one(input)?;
assert_eq!(result, expected);

View file

@ -1,7 +1,7 @@
use super::template::{DayTrait, ResultType};
use crate::common::{
parser::{eol_terminated, extract_result, ignore, trim0},
pos::Pos,
pos2::Pos2,
};
use itertools::Itertools;
use nom::{
@ -44,7 +44,7 @@ impl DayTrait for Day {
.iter()
.tuple_combinations()
.filter_map(|(first, second)| first.cross(second))
.filter(|pos| sensors.iter().all(|sensor| !sensor.contains(pos)))
.filter(|pos| sensors.iter().all(|sensor| !sensor.contains(*pos)))
.dedup()
.collect::<Vec<_>>();
@ -58,7 +58,7 @@ impl DayTrait for Day {
}
impl Day {
fn parse_all(lines: &str) -> Result<(HashSet<Sensor>, HashSet<Pos<i64>>), SensorError> {
fn parse_all(lines: &str) -> Result<(HashSet<Sensor>, HashSet<Pos2<i64>>), SensorError> {
let data = extract_result(many0(eol_terminated(Sensor::parse)))(lines)?;
let mut sensors = HashSet::new();
@ -70,7 +70,7 @@ impl Day {
Ok((sensors, beacons))
}
fn count_coverage_at(sensors: &HashSet<Sensor>, beacons: &HashSet<Pos<i64>>, row: i64) -> i64 {
fn count_coverage_at(sensors: &HashSet<Sensor>, beacons: &HashSet<Pos2<i64>>, row: i64) -> i64 {
let ranges = sensors
.iter()
.filter_map(|sensor| sensor.range_at(row))
@ -117,7 +117,7 @@ impl From<Err<Error<&str>>> for SensorError {
#[derive(Debug)]
struct Line {
start: Pos<i64>,
start: Pos2<i64>,
is_up: bool,
steps: i64,
}
@ -139,16 +139,16 @@ impl Line {
if one.pos.y() < two.pos.y() {
is_up = true;
if one.pos.y() + one.radius <= two.pos.y() {
start = Pos::new(one.pos.x(), one.pos.y() + one.radius + 1);
start = Pos2::new(one.pos.x(), one.pos.y() + one.radius + 1);
} else {
start = Pos::new(two.pos.x() - two.radius - 1, two.pos.y());
start = Pos2::new(two.pos.x() - two.radius - 1, two.pos.y());
}
} else {
is_up = false;
if one.pos.y() - one.radius >= two.pos.y() {
start = Pos::new(one.pos.x(), one.pos.y() - one.radius - 1);
start = Pos2::new(one.pos.x(), one.pos.y() - one.radius - 1);
} else {
start = Pos::new(two.pos.x() - two.radius - 1, two.pos.y());
start = Pos2::new(two.pos.x() - two.radius - 1, two.pos.y());
}
}
let steps = two.pos.x().min(one.pos.x() + one.radius) - start.x();
@ -160,7 +160,7 @@ impl Line {
})
}
fn cross(&self, other: &Line) -> Option<Pos<i64>> {
fn cross(&self, other: &Line) -> Option<Pos2<i64>> {
if self.is_up == other.is_up {
return None;
}
@ -181,7 +181,7 @@ impl Line {
return None;
}
let pos = top_down.start + Pos::splat(r);
let pos = top_down.start + Pos2::splat(r);
Some(pos)
}
}
@ -227,7 +227,7 @@ impl Range {
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
struct Sensor {
pos: Pos<i64>,
pos: Pos2<i64>,
radius: i64,
}
@ -239,21 +239,21 @@ impl Sensor {
}
}
fn parse_pos(input: &str) -> IResult<&str, Pos<i64>> {
fn parse_pos(input: &str) -> IResult<&str, Pos2<i64>> {
let (input, (x, y)) = separated_pair(
Sensor::component("x"),
trim0(char(',')),
Sensor::component("y"),
)(input)?;
Ok((input, Pos::new(x, y)))
Ok((input, Pos2::new(x, y)))
}
pub fn parse(input: &str) -> IResult<&str, (Sensor, Pos<i64>)> {
pub fn parse(input: &str) -> IResult<&str, (Sensor, Pos2<i64>)> {
let input = ignore(tag("Sensor at"))(input)?;
let (input, pos) = trim0(Sensor::parse_pos)(input)?;
let input = ignore(tag(": closest beacon is at"))(input)?;
let (input, beacon) = trim0(Sensor::parse_pos)(input)?;
let radius = pos.taxicab(&beacon);
let radius = pos.taxicab_between(beacon);
Ok((input, (Sensor { pos, radius }, beacon)))
}
@ -268,12 +268,12 @@ impl Sensor {
}
pub fn border_distance(&self, other: &Sensor) -> i64 {
let distance = self.pos.taxicab(&other.pos);
let distance = self.pos.taxicab_between(other.pos);
distance - self.radius - other.radius
}
pub fn contains(&self, pos: &Pos<i64>) -> bool {
self.pos.taxicab(pos) <= self.radius
pub fn contains(&self, pos: Pos2<i64>) -> bool {
self.pos.taxicab_between(pos) <= self.radius
}
}
@ -287,10 +287,10 @@ mod test {
let input = "Sensor at x=2, y=18: closest beacon is at x=-2, y=15";
let expected = (
Sensor {
pos: Pos::new(2, 18),
pos: Pos2::new(2, 18),
radius: 7,
},
Pos::new(-2, 15),
Pos2::new(-2, 15),
);
let result = extract_result(Sensor::parse)(input)?;
assert_eq!(result, expected);
@ -301,7 +301,7 @@ mod test {
#[test]
fn test_width() {
let sensor = Sensor {
pos: Pos::new(8, 7),
pos: Pos2::new(8, 7),
radius: 9,
};
assert_eq!(sensor.range_at(17), None);

View file

@ -1,5 +1,8 @@
use super::template::{DayTrait, ResultType};
use crate::common::parser::{eol_terminated, extract_result, ignore, trim0, trim1, trim_left1};
use crate::common::{
name::Name,
parser::{eol_terminated, extract_result, ignore, trim0, trim1, trim_left1},
};
use itertools::Itertools;
use nom::{
branch::alt,
@ -95,6 +98,7 @@ impl Mul<Flow> for Time {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
struct Flow(i64);
impl Deref for Flow {
type Target = i64;
@ -208,7 +212,7 @@ impl<'a> TryFrom<&'a str> for RawValve<'a> {
#[derive(Debug)]
struct Valve {
id: String,
id: Name,
idx: Index,
flow: Flow,
distances: Vec<Time>,
@ -245,7 +249,7 @@ impl Valve {
distances.sort_unstable_by_key(|(id, _)| *id);
Ok(Valve {
id: valve.id.to_owned(),
id: valve.id.into(),
idx,
flow: valve.flow,
distances: distances
@ -257,7 +261,7 @@ impl Valve {
fn zero_flow(valve: &RawValve, idx: Index) -> Valve {
Valve {
id: valve.id.to_owned(),
id: valve.id.into(),
idx,
flow: valve.flow,
distances: vec![],
@ -309,11 +313,12 @@ impl ValveSystem {
}
#[allow(dead_code)]
fn get_valve_by_id(&self, id: &str) -> Result<&Valve, ValveError> {
fn get_valve_by_id<N: Into<Name>>(&self, id: N) -> Result<&Valve, ValveError> {
let id = id.into();
self.valves
.iter()
.find(|valve| valve.id == id)
.ok_or(ValveError::ValveNotFound(id.to_owned()))
.ok_or(ValveError::ValveNotFound(id.to_string()))
}
fn get_valve(&self, idx: Index) -> &Valve {

View file

@ -1,9 +1,7 @@
use std::{cell::Cell, marker::PhantomData, ops::Index};
use crate::common::file::{read_string, split_lines};
use super::template::{DayTrait, ResultType};
use crate::common::file::{read_string, split_lines};
use itertools::Itertools;
use std::{cell::Cell, marker::PhantomData, ops::Index};
use thiserror::Error;
const DAY_NUMBER: usize = 17;

View file

@ -1,11 +1,9 @@
use super::template::{DayTrait, ResultType};
use crate::common::file::split_lines;
use itertools::Itertools;
use std::{collections::HashSet, num::ParseIntError};
use thiserror::Error;
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
const DAY_NUMBER: usize = 18;
pub struct Day;
@ -173,7 +171,7 @@ impl Blob {
}
pub fn count_outside(&self) -> i64 {
let Some(mut extent)= self.extent() else {
let Some(mut extent) = self.extent() else {
return 0;
};
let mut sides = 0;

372
src/days/day21/mod.rs Normal file
View file

@ -0,0 +1,372 @@
use super::template::{DayTrait, ResultType};
use crate::common::{
name::Name,
parser::{extract_result, ignore, trim0},
};
use nom::{
branch::alt,
character::complete::{alpha1, char, i64, multispace0, one_of},
error::Error,
multi::many0,
sequence::{terminated, tuple},
Err, IResult, Parser,
};
use std::{collections::HashMap, str::FromStr};
use thiserror::Error;
const DAY_NUMBER: usize = 21;
const ROOT: &'static str = "root";
const HUMAN: &'static str = "humn";
pub struct Day;
impl DayTrait for Day {
fn get_day_number(&self) -> usize {
DAY_NUMBER
}
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
let troop: Troop = lines.parse()?;
Ok(ResultType::Integer(troop.value(ROOT)?))
}
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
let troop: Troop = lines.parse()?;
Ok(ResultType::Integer(troop.make_equal(ROOT)?))
}
}
#[derive(Debug, Error)]
enum MonkeyError {
#[error("Not a valid description: {0}")]
ParsingError(String),
#[error("Unknown Monkey: {0}")]
UnknownMonkey(String),
#[error("Not an algebraic Operation")]
NoAlgebraicOperation,
#[error("There are too many Humans in this input")]
TooManyHumans,
#[error("Unknown operand: {0}")]
UnknownOperand(char),
}
impl From<Err<Error<&str>>> for MonkeyError {
fn from(error: Err<Error<&str>>) -> Self {
MonkeyError::ParsingError(error.to_string())
}
}
#[derive(Debug, PartialEq, Eq)]
enum MonkeyJob {
Yell(i64),
Add(Name, Name),
Sub(Name, Name),
Mul(Name, Name),
Div(Name, Name),
}
impl MonkeyJob {
pub fn new<N1: Into<Name>, N2: Into<Name>>(
operand: char,
name1: N1,
name2: N2,
) -> Result<Self, MonkeyError> {
match operand {
'+' => Ok(MonkeyJob::Add(name1.into(), name2.into())),
'-' => Ok(MonkeyJob::Sub(name1.into(), name2.into())),
'*' => Ok(MonkeyJob::Mul(name1.into(), name2.into())),
'/' => Ok(MonkeyJob::Div(name1.into(), name2.into())),
c => Err(MonkeyError::UnknownOperand(c)),
}
}
fn parse_operation(input: &str) -> IResult<&str, MonkeyJob> {
tuple((alpha1, trim0(one_of("+-*/")), alpha1))
.map(|(name1, operand, name2)| MonkeyJob::new(operand, name1, name2).unwrap())
.parse(input)
}
fn parse(input: &str) -> IResult<&str, MonkeyJob> {
trim0(alt((
i64.map(|val| MonkeyJob::Yell(val)),
MonkeyJob::parse_operation,
)))
.parse(input)
}
fn perform(&self, troop: &Troop) -> Result<i64, MonkeyError> {
Ok(match self {
MonkeyJob::Yell(value) => *value,
MonkeyJob::Add(name1, name2) => {
troop.value(name1.clone())? + troop.value(name2.clone())?
}
MonkeyJob::Sub(name1, name2) => {
troop.value(name1.clone())? - troop.value(name2.clone())?
}
MonkeyJob::Mul(name1, name2) => {
troop.value(name1.clone())? * troop.value(name2.clone())?
}
MonkeyJob::Div(name1, name2) => {
troop.value(name1.clone())? / troop.value(name2.clone())?
}
})
}
fn get_names(&self) -> Result<(Name, Name), MonkeyError> {
match self {
MonkeyJob::Add(name1, name2)
| MonkeyJob::Sub(name1, name2)
| MonkeyJob::Mul(name1, name2)
| MonkeyJob::Div(name1, name2) => Ok((name1.clone(), name2.clone())),
MonkeyJob::Yell(_) => Err(MonkeyError::NoAlgebraicOperation),
}
}
fn perform_reverse(&self, troop: &Troop) -> Result<HumanJob, MonkeyError> {
match self {
MonkeyJob::Yell(value) => Ok(HumanJob::Const(*value)),
MonkeyJob::Add(name1, name2) => match troop
.shouting_match(name1.clone(), name2.clone())?
{
(HumanJob::Const(val1), HumanJob::Const(val2)) => Ok(HumanJob::Const(val1 + val2)),
(other, HumanJob::Const(val2)) => Ok(HumanJob::Add(Box::new(other), val2)),
(HumanJob::Const(val1), other) => Ok(HumanJob::Add(Box::new(other), val1)),
_ => Err(MonkeyError::TooManyHumans),
},
MonkeyJob::Sub(name1, name2) => match troop
.shouting_match(name1.clone(), name2.clone())?
{
(HumanJob::Const(val1), HumanJob::Const(val2)) => Ok(HumanJob::Const(val1 - val2)),
(other, HumanJob::Const(val2)) => Ok(HumanJob::Sub1(Box::new(other), val2)),
(HumanJob::Const(val1), other) => Ok(HumanJob::Sub2(val1, Box::new(other))),
_ => Err(MonkeyError::TooManyHumans),
},
MonkeyJob::Mul(name1, name2) => match troop
.shouting_match(name1.clone(), name2.clone())?
{
(HumanJob::Const(val1), HumanJob::Const(val2)) => Ok(HumanJob::Const(val1 * val2)),
(other, HumanJob::Const(val2)) => Ok(HumanJob::Mul(Box::new(other), val2)),
(HumanJob::Const(val1), other) => Ok(HumanJob::Mul(Box::new(other), val1)),
_ => Err(MonkeyError::TooManyHumans),
},
MonkeyJob::Div(name1, name2) => match troop
.shouting_match(name1.clone(), name2.clone())?
{
(HumanJob::Const(val1), HumanJob::Const(val2)) => Ok(HumanJob::Const(val1 / val2)),
(other, HumanJob::Const(val2)) => Ok(HumanJob::Div1(Box::new(other), val2)),
(HumanJob::Const(val1), other) => Ok(HumanJob::Div2(val1, Box::new(other))),
_ => Err(MonkeyError::TooManyHumans),
},
}
}
}
enum HumanJob {
Human,
Const(i64),
Add(Box<HumanJob>, i64),
Sub1(Box<HumanJob>, i64),
Sub2(i64, Box<HumanJob>),
Mul(Box<HumanJob>, i64),
Div1(Box<HumanJob>, i64),
Div2(i64, Box<HumanJob>),
}
impl HumanJob {
fn calc(&self, result: i64) -> i64 {
match self {
HumanJob::Human => result,
HumanJob::Add(job, value) => job.calc(result - value),
HumanJob::Sub1(job, value) => job.calc(result + value),
HumanJob::Sub2(value, job) => job.calc(value - result),
HumanJob::Mul(job, value) => job.calc(result / value),
HumanJob::Div1(job, value) => job.calc(result * value),
HumanJob::Div2(value, job) => job.calc(value / result),
HumanJob::Const(_) => {
unreachable!("There can be not Const in a legal HumanJob tree")
}
}
}
}
#[derive(Debug, PartialEq, Eq)]
struct Monkey {
name: Name,
job: MonkeyJob,
}
impl Monkey {
fn new<N: Into<Name>>(name: N, job: MonkeyJob) -> Self {
Monkey {
name: name.into(),
job,
}
}
fn parse(input: &str) -> IResult<&str, Monkey> {
let (input, name) = trim0(alpha1)(input)?;
let input = ignore(trim0(char(':')))(input)?;
let (input, job) = MonkeyJob::parse(input)?;
Ok((input, Monkey::new(name, job)))
}
fn shouting(&self, troop: &Troop) -> Result<i64, MonkeyError> {
self.job.perform(troop)
}
fn reverse_shouting(&self, troop: &Troop) -> Result<HumanJob, MonkeyError> {
if self.name.as_str() == HUMAN {
return Ok(HumanJob::Human);
}
self.job.perform_reverse(troop)
}
}
impl FromStr for Monkey {
type Err = MonkeyError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Ok(extract_result(Monkey::parse)(input)?)
}
}
struct Troop {
monkeys: HashMap<Name, Monkey>,
}
impl Troop {
pub fn get_monkey<N: Into<Name>>(&self, name: N) -> Result<&Monkey, MonkeyError> {
let name = name.into();
self.monkeys
.get(&name)
.ok_or_else(|| MonkeyError::UnknownMonkey(String::from(name.as_str())))
}
pub fn value<N: Into<Name>>(&self, name: N) -> Result<i64, MonkeyError> {
self.get_monkey(name)?.shouting(self)
}
pub fn make_equal<N: Into<Name>>(&self, name: N) -> Result<i64, MonkeyError> {
let monkey = self.get_monkey(name)?;
let (name1, name2) = monkey.job.get_names()?;
let monkey1 = self.get_monkey(name1)?;
let monkey2 = self.get_monkey(name2)?;
match (
monkey1.reverse_shouting(self)?,
monkey2.reverse_shouting(self)?,
) {
(job, HumanJob::Const(value)) | (HumanJob::Const(value), job) => Ok(job.calc(value)),
_ => Err(MonkeyError::TooManyHumans),
}
}
fn shouting_match<N1: Into<Name>, N2: Into<Name>>(
&self,
name1: N1,
name2: N2,
) -> Result<(HumanJob, HumanJob), MonkeyError> {
let monkey1 = self.get_monkey(name1)?;
let monkey2 = self.get_monkey(name2)?;
Ok((
monkey1.reverse_shouting(self)?,
monkey2.reverse_shouting(self)?,
))
}
}
impl FromStr for Troop {
type Err = MonkeyError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let monkeys = extract_result(many0(terminated(Monkey::parse, multispace0)))(input)?;
let monkeys: HashMap<Name, Monkey> = monkeys
.into_iter()
.map(|monkey| (monkey.name.clone(), monkey))
.collect();
Ok(Troop { monkeys })
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::common::file::read_string;
use anyhow::Result;
#[test]
fn test_part1() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(152);
let result = day.part1(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_part2() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(301);
let result = day.part2(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn parse_lines() -> Result<()> {
let input = "root: pppw + sjmn";
let expected = Monkey {
name: Name::from("root"),
job: MonkeyJob::Add(Name::from("pppw"), Name::from("sjmn")),
};
let result: Monkey = input.parse()?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn parse_troop() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let troop: Troop = lines.parse()?;
assert_eq!(troop.monkeys.len(), 15);
assert_eq!(
troop.monkeys[&Name::from("sjmn")],
Monkey::new(
"sjmn",
MonkeyJob::Mul(Name::from("drzm"), Name::from("dbpl"))
)
);
Ok(())
}
#[test]
fn values() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let troop: Troop = lines.parse()?;
assert_eq!(troop.value(Name::from("lfqf"))?, 4);
assert_eq!(troop.value(Name::from("root"))?, 152);
Ok(())
}
#[test]
fn make_equal() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let troop: Troop = lines.parse()?;
assert_eq!(troop.make_equal(Name::from("root"))?, 301);
Ok(())
}
}

731
src/days/day22/mod.rs Normal file
View file

@ -0,0 +1,731 @@
use super::template::{DayTrait, ResultType};
use crate::common::{
direction::Direction,
parser::{eol_terminated, extract_result, ignore, usize},
pos2::Pos2,
unit_vector::{self, UnitVector},
};
use nom::{
branch::alt,
character::complete::{char, multispace0},
combinator::value,
error::Error,
multi::many1,
Err, IResult, Parser,
};
use num_traits::Zero;
use std::{collections::HashMap, str::FromStr};
use thiserror::Error;
const DAY_NUMBER: usize = 22;
pub struct Day;
impl DayTrait for Day {
fn get_day_number(&self) -> usize {
DAY_NUMBER
}
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
let (world_map, instructions) = Day::parse(lines)?;
let result = WrappingWalker::walk_all(world_map, instructions)?;
Ok(ResultType::Integer(result.value()))
}
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
let (world_map, instructions) = Day::parse(lines)?;
let result = CubeWalker::walk_all(world_map, instructions)?;
Ok(ResultType::Integer(result.value()))
}
}
impl Day {
fn parse(input: &str) -> Result<(WorldMap, Vec<Instruction>), WorldError> {
let (input, world) = WorldMap::parse(input)?;
let input = ignore(multispace0)(input)?;
let instructions = extract_result(Instruction::parse)(input)?;
Ok((world, instructions))
}
}
#[derive(Debug, Error)]
enum WorldError {
#[error("Not a valid description: {0}")]
ParsingError(String),
#[error("No Starting Point found")]
NoStartingPoint,
#[error("Map is not a valid cube")]
NotAValidCube,
}
impl From<Err<Error<&str>>> for WorldError {
fn from(error: Err<Error<&str>>) -> Self {
WorldError::ParsingError(error.to_string())
}
}
struct WorldMap {
tiles: Vec<Vec<Option<bool>>>,
}
impl FromStr for WorldMap {
type Err = WorldError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Ok(extract_result(WorldMap::parse)(input)?)
}
}
impl WorldMap {
pub fn new(tiles: Vec<Vec<Option<bool>>>) -> Self {
Self { tiles }
}
pub fn height(&self) -> usize {
self.tiles.len()
}
pub fn width(&self) -> Option<usize> {
self.tiles.iter().map(|row| row.len()).max()
}
fn parse(input: &str) -> IResult<&str, WorldMap> {
let tile = alt((
value(Some(false), char('#')),
value(Some(true), char('.')),
value(None, char(' ')),
));
let line = eol_terminated(many1(tile));
let mut lines = many1(line).map(|tiles| WorldMap::new(tiles));
lines.parse(input)
}
fn get_first(&self, map_pos: Location) -> Option<Location> {
match map_pos.facing {
Direction::East => self.tiles.get(map_pos.pos.y()).and_then(|row| {
row.iter()
.position(|t| t.is_some())
.map(|x| Pos2::new(x, map_pos.pos.y()))
}),
Direction::West => self.tiles.get(map_pos.pos.y()).and_then(|row| {
row.iter()
.rposition(|t| t.is_some())
.map(|x| Pos2::new(x, map_pos.pos.y()))
}),
Direction::South => self
.tiles
.iter()
.position(|row| map_pos.pos.x() < row.len() && row[map_pos.pos.x()].is_some())
.map(|y| Pos2::new(map_pos.pos.x(), y)),
Direction::North => self
.tiles
.iter()
.rposition(|row| map_pos.pos.x() < row.len() && row[map_pos.pos.x()].is_some())
.map(|y| Pos2::new(map_pos.pos.x(), y)),
}
.map(|pos| Location::new(pos, map_pos.facing))
}
fn get_tile(&self, pos: Pos2<usize>) -> Option<bool> {
self.tiles
.get(pos.y())
.and_then(|row| row.get(pos.x()))
.copied()
.flatten()
}
}
#[derive(Debug, Clone)]
enum Instruction {
Walk(usize),
Right,
Left,
}
impl Instruction {
fn parse(input: &str) -> IResult<&str, Vec<Instruction>> {
many1(alt((
usize.map(|v| Instruction::Walk(v)),
value(Instruction::Right, char('R')),
value(Instruction::Left, char('L')),
)))
.parse(input)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct Location {
pos: Pos2<usize>,
facing: Direction,
}
impl Location {
pub fn new(pos: Pos2<usize>, facing: Direction) -> Self {
Location { pos, facing }
}
#[inline]
fn pos(&self) -> Pos2<usize> {
self.pos
}
fn one_step(&self) -> Option<Self> {
self.pos
.check_add(self.facing)
.map(|pos| Location::new(pos, self.facing))
}
pub fn value(&self) -> i64 {
(self.pos.y() + 1) as i64 * 1000 + (self.pos.x() + 1) as i64 * 4 + self.face_value()
}
fn face_value(&self) -> i64 {
match self.facing {
Direction::East => 0,
Direction::North => 3,
Direction::West => 2,
Direction::South => 1,
}
}
fn turn_left(&mut self) {
self.facing = self.facing.turn_left()
}
fn turn_right(&mut self) {
self.facing = self.facing.turn_right()
}
}
trait Walker: Sized {
fn turn_right(&mut self);
fn turn_left(&mut self);
fn set_location(&mut self, pos: Location);
fn new(world_map: WorldMap) -> Result<Self, WorldError>;
fn act(&mut self, instruction: Instruction) {
match instruction {
Instruction::Right => self.turn_right(),
Instruction::Left => self.turn_left(),
Instruction::Walk(steps) => {
for _ in 0..steps {
let Some(next_pos) = self.step() else {
break;
};
self.set_location(next_pos);
}
}
}
}
fn get_map(&self) -> &WorldMap;
fn location(&self) -> Location;
fn wrap(&self) -> Option<Location>;
fn step(&self) -> Option<Location> {
if let Some(next_pos) = self.location().one_step() {
match self.get_map().get_tile(next_pos.pos()) {
Some(true) => return Some(next_pos),
Some(false) => return None,
None => {}
}
};
if let Some(next_pos) = self.wrap() {
match self.get_map().get_tile(next_pos.pos()) {
Some(true) => return Some(next_pos),
Some(false) => return None,
None => {}
}
};
unreachable!()
}
fn walk_all(world_map: WorldMap, instructions: Vec<Instruction>) -> Result<Location, WorldError>
where
Self: Sized,
{
let mut walker = Self::new(world_map)?;
for instruction in instructions {
walker.act(instruction);
}
Ok(walker.location())
}
}
struct WrappingWalker {
world: WorldMap,
location: Location,
}
impl Walker for WrappingWalker {
fn new(world: WorldMap) -> Result<Self, WorldError> {
if let Some(location) = world.get_first(Location::new(Pos2::new(0, 0), Direction::East)) {
Ok(WrappingWalker { world, location })
} else {
Err(WorldError::NoStartingPoint)
}
}
fn wrap(&self) -> Option<Location> {
self.world.get_first(self.location)
}
fn turn_right(&mut self) {
self.location.turn_right();
}
fn turn_left(&mut self) {
self.location.turn_left();
}
fn set_location(&mut self, pos: Location) {
self.location = pos;
}
fn location(&self) -> Location {
self.location
}
fn get_map(&self) -> &WorldMap {
&self.world
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct CubePos {
side: UnitVector,
facing: UnitVector,
}
impl CubePos {
fn new(side: UnitVector, facing: UnitVector) -> Self {
CubePos { side, facing }
}
fn facing_east(&self, facing: Direction) -> UnitVector {
match facing {
Direction::East => self.facing,
Direction::North => -(self.side * self.facing),
Direction::West => -self.facing,
Direction::South => self.side * self.facing,
}
}
fn forward(&self) -> CubePos {
CubePos {
side: self.facing,
facing: -self.side,
}
}
#[allow(dead_code)]
fn turn_right(&mut self) {
self.facing = -(self.side * self.facing);
}
fn turn_left(&mut self) {
self.facing = self.side * self.facing;
}
}
struct CubeWalker {
world: WorldMap,
location: Location,
translation: HashMap<UnitVector, (Pos2<usize>, UnitVector)>,
side_length: usize,
}
impl CubeWalker {
fn get_side_length(world: &WorldMap) -> Result<usize, WorldError> {
let width = world.width().ok_or_else(|| WorldError::NotAValidCube)?;
let height = world.height();
if width % 3 == 0 && height % 4 == 0 && width / 3 == height / 4 {
Ok(height / 4)
} else if width % 4 == 0 && height % 3 == 0 && width / 4 == height / 3 {
Ok(width / 4)
} else if width % 5 == 0 && height % 2 == 0 && width / 5 == height / 2 {
Ok(height / 2)
} else if width % 2 == 0 && height % 5 == 0 && width / 2 == height / 5 {
Ok(width / 2)
} else {
Err(WorldError::NotAValidCube)
}
}
fn analyze_cube(
world_map: &WorldMap,
mut normal_map_pos: Location,
mut cube_pos: CubePos,
mut sides: HashMap<UnitVector, (Pos2<usize>, UnitVector)>,
side_length: usize,
) -> HashMap<UnitVector, (Pos2<usize>, UnitVector)> {
let facing_east = cube_pos.facing_east(normal_map_pos.facing);
sides.insert(cube_pos.side, (normal_map_pos.pos, facing_east));
if sides.len() == 6 {
return sides;
}
for _ in 0..4 {
if let Some(next_normal) = normal_map_pos.one_step() {
if world_map
.get_tile(next_normal.pos() * side_length)
.is_some()
{
let next_cube = cube_pos.forward();
if !sides.contains_key(&next_cube.side) {
sides = CubeWalker::analyze_cube(
world_map,
next_normal,
next_cube,
sides,
side_length,
);
}
}
}
normal_map_pos.turn_left();
cube_pos.turn_left();
}
sides
}
fn get_cube_position(&self, location: Location) -> Option<(CubePos, usize)> {
let normal = location.pos / self.side_length;
for (side, (pos, right)) in self.translation.iter() {
if pos == &normal {
match location.facing {
Direction::East => {
return Some((
CubePos::new(*side, *right),
location.pos.y() - pos.y() * self.side_length,
))
}
Direction::North => {
return Some((
CubePos::new(*side, -(*right * *side)),
location.pos.x() - pos.x() * self.side_length,
))
}
Direction::West => {
return Some((
CubePos::new(*side, -*right),
(pos.y() + 1) * self.side_length - location.pos.y() - 1,
))
}
Direction::South => {
return Some((
CubePos::new(*side, *right * *side),
(pos.x() + 1) * self.side_length - location.pos.x() - 1,
))
}
}
}
}
None
}
}
impl Walker for CubeWalker {
fn new(world: WorldMap) -> Result<Self, WorldError> {
if let Some(location) = world.get_first(Location::new(Pos2::zero(), Direction::East)) {
let side_length = CubeWalker::get_side_length(&world)?;
let normal_map_pos = Location::new(location.pos / side_length, location.facing);
let translation = CubeWalker::analyze_cube(
&world,
normal_map_pos,
CubePos::new(unit_vector::X, unit_vector::Y),
HashMap::new(),
side_length,
);
if translation.len() != 6 {
return Err(WorldError::NotAValidCube);
}
Ok(CubeWalker {
world,
location,
translation,
side_length,
})
} else {
Err(WorldError::NoStartingPoint)
}
}
fn wrap(&self) -> Option<Location> {
let (old_pos, delta) = self.get_cube_position(self.location)?;
let new_pos = old_pos.forward();
let (position, normal_right) = self.translation[&new_pos.side];
let normal = CubePos::new(new_pos.side, normal_right);
if new_pos.facing == normal.facing {
Some(Location::new(
Pos2::new(
position.x() * self.side_length,
position.y() * self.side_length + delta,
),
Direction::East,
))
} else if new_pos.facing == -normal.facing {
Some(Location::new(
Pos2::new(
(position.x() + 1) * self.side_length - 1,
(position.y() + 1) * self.side_length - 1 - delta,
),
Direction::West,
))
} else if new_pos.facing == (normal.side * normal.facing) {
Some(Location::new(
Pos2::new(
position.x() * self.side_length + delta,
(position.y() + 1) * self.side_length - 1,
),
Direction::North,
))
} else {
Some(Location::new(
Pos2::new(
(position.x() + 1) * self.side_length - 1 - delta,
position.y() * self.side_length,
),
Direction::South,
))
}
}
fn turn_right(&mut self) {
self.location.turn_right();
}
fn turn_left(&mut self) {
self.location.turn_left();
}
fn set_location(&mut self, location: Location) {
self.location = location;
}
fn location(&self) -> Location {
self.location
}
fn get_map(&self) -> &WorldMap {
&self.world
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::common::file::read_string;
use anyhow::Result;
#[test]
fn test_part1() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(6032);
let result = day.part1(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_part2() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(5031);
let result = day.part2(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn parse() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let (world, instructions) = Day::parse(&lines)?;
assert_eq!(world.tiles.len(), 12);
assert_eq!(instructions.len(), 13);
Ok(())
}
#[test]
fn walk() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let (world_map, _) = Day::parse(&lines)?;
let mut walker = WrappingWalker::new(world_map)?;
assert_eq!(
walker.location,
Location::new(Pos2::new(8, 0), Direction::East)
);
walker.act(Instruction::Walk(10));
assert_eq!(
walker.location,
Location::new(Pos2::new(10, 0), Direction::East)
);
walker.act(Instruction::Left);
walker.act(Instruction::Walk(2));
assert_eq!(
walker.location,
Location::new(Pos2::new(10, 10), Direction::North)
);
Ok(())
}
#[test]
fn walk_all() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let (world_map, instructions) = Day::parse(&lines)?;
let result = WrappingWalker::walk_all(world_map, instructions)?;
assert_eq!(result.pos, Pos2::new(7, 5));
assert_eq!(result.facing, Direction::East);
assert_eq!(result.value(), 6032);
Ok(())
}
#[test]
fn parse_cube() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let (world_map, _) = Day::parse(&lines)?;
let walker = CubeWalker::new(world_map)?;
assert_eq!(walker.side_length, 4);
assert_eq!(
walker.translation.get(&unit_vector::X),
Some(&(Pos2::new(2, 0), unit_vector::Y))
);
assert_eq!(
walker.translation.get(&unit_vector::NEG_X),
Some(&(Pos2::new(2, 2), unit_vector::Y))
);
assert_eq!(
walker.translation.get(&unit_vector::Y),
Some(&(Pos2::new(3, 2), unit_vector::X))
);
assert_eq!(
walker.translation.get(&unit_vector::NEG_Y),
Some(&(Pos2::new(1, 1), unit_vector::NEG_Z))
);
assert_eq!(
walker.translation.get(&unit_vector::Z),
Some(&(Pos2::new(0, 1), unit_vector::NEG_Y))
);
assert_eq!(
walker.translation.get(&unit_vector::NEG_Z),
Some(&(Pos2::new(2, 1), unit_vector::Y))
);
Ok(())
}
#[test]
fn cube_position() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let (world_map, _) = Day::parse(&lines)?;
let walker = CubeWalker::new(world_map)?;
let pos = Location::new(Pos2::new(11, 5), Direction::East);
let expected = (CubePos::new(unit_vector::NEG_Z, unit_vector::Y), 1);
assert_eq!(walker.get_cube_position(pos), Some(expected));
let pos = Location::new(Pos2::new(10, 11), Direction::South);
let expected = (CubePos::new(unit_vector::NEG_X, unit_vector::Z), 1);
assert_eq!(walker.get_cube_position(pos), Some(expected));
Ok(())
}
#[test]
fn cube_wrapping() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let (world_map, _) = Day::parse(&lines)?;
let mut walker = CubeWalker::new(world_map)?;
walker.location = Location::new(Pos2::new(11, 5), Direction::East);
let expected = Location::new(Pos2::new(14, 8), Direction::South);
assert_eq!(walker.wrap(), Some(expected));
walker.location = Location::new(Pos2::new(10, 11), Direction::South);
let expected = Location::new(Pos2::new(1, 7), Direction::North);
assert_eq!(walker.wrap(), Some(expected));
walker.location = Location::new(Pos2::new(14, 8), Direction::North);
let expected = Location::new(Pos2::new(11, 5), Direction::West);
assert_eq!(walker.wrap(), Some(expected));
walker.location = Location::new(Pos2::new(1, 7), Direction::South);
let expected = Location::new(Pos2::new(10, 11), Direction::North);
assert_eq!(walker.wrap(), Some(expected));
Ok(())
}
#[test]
fn walk_cube() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let (world_map, _) = Day::parse(&lines)?;
let mut walker = CubeWalker::new(world_map)?;
assert_eq!(
walker.location,
Location::new(Pos2::new(8, 0), Direction::East)
);
walker.act(Instruction::Walk(10));
assert_eq!(
walker.location,
Location::new(Pos2::new(10, 0), Direction::East)
);
walker.act(Instruction::Left);
walker.act(Instruction::Walk(2));
assert_eq!(
walker.location,
Location::new(Pos2::new(1, 5), Direction::South)
);
Ok(())
}
#[test]
fn walk_all_cube() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let (world_map, instructions) = Day::parse(&lines)?;
let result = CubeWalker::walk_all(world_map, instructions)?;
assert_eq!(result.pos, Pos2::new(6, 4));
assert_eq!(result.facing, Direction::North);
assert_eq!(result.value(), 5031);
Ok(())
}
}

336
src/days/day23/mod.rs Normal file
View file

@ -0,0 +1,336 @@
use super::template::{DayTrait, ResultType};
use crate::common::{area::Area, direction::Direction, pos2::Pos2};
use itertools::Itertools;
use std::{collections::HashMap, str::FromStr};
use thiserror::Error;
const DAY_NUMBER: usize = 23;
pub struct Day;
impl DayTrait for Day {
fn get_day_number(&self) -> usize {
DAY_NUMBER
}
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
let field: Field = lines.parse()?;
let elves = field.rounds(10);
Ok(ResultType::Integer(count_empty_tiles(&elves)))
}
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
let field: Field = lines.parse()?;
let round = field.forever() as i64;
Ok(ResultType::Integer(round))
}
}
fn count_empty_tiles(elves: &[Pos2<i32>]) -> i64 {
let Some(area) = Area::from_iterator(elves.iter()) else {
return 0;
};
area.area() as i64 - elves.len() as i64
}
#[derive(Debug, Error)]
enum FieldError {
#[error("Unknown Char: {0}")]
UnknownChar(char),
}
fn next_direction(current: Direction) -> Direction {
match current {
Direction::North => Direction::South,
Direction::South => Direction::West,
Direction::West => Direction::East,
Direction::East => Direction::North,
}
}
struct DirectionDispenser(Direction);
impl DirectionDispenser {
fn new() -> DirectionDispenser {
Self(Direction::North)
}
fn progress(&mut self) {
self.0 = next_direction(self.0);
}
fn iter(&self) -> DispenserIterator {
DispenserIterator::new(self.0)
}
}
struct DispenserIterator {
start: Direction,
current: Option<Direction>,
}
impl DispenserIterator {
pub fn new(start: Direction) -> Self {
DispenserIterator {
start,
current: Some(start),
}
}
}
impl Iterator for DispenserIterator {
type Item = Direction;
fn next(&mut self) -> Option<Self::Item> {
let result = self.current;
if let Some(current) = self.current {
let next = next_direction(current);
if next == self.start {
self.current = None;
} else {
self.current = Some(next);
}
}
result
}
}
struct PosIterator {
pos: Pos2<i32>,
inc: Pos2<i32>,
count: usize,
}
impl PosIterator {
fn new(pos: Pos2<i32>, direction: Direction) -> Self {
let inc = direction.turn_right().into();
let pos = pos + direction - inc * 2;
Self { pos, inc, count: 0 }
}
}
impl Iterator for PosIterator {
type Item = Pos2<i32>;
fn next(&mut self) -> Option<Self::Item> {
if self.count > 2 {
return None;
}
self.pos += self.inc;
self.count += 1;
Some(self.pos)
}
}
enum MoveCheck {
NoNeighbors,
NoFreeSpace,
MoveTo(Pos2<i32>, Direction),
}
struct Field {
direction: DirectionDispenser,
elves: HashMap<Pos2<i32>, bool>,
}
impl FromStr for Field {
type Err = FieldError;
fn from_str(lines: &str) -> Result<Self, Self::Err> {
let elves: HashMap<Pos2<i32>, bool> = lines
.split('\n')
.enumerate()
.map(|(y, row)| {
row.chars().enumerate().filter_map(move |(x, c)| match c {
'.' => None,
'#' => Some(Ok((Pos2::new(x as i32, y as i32), true))),
_ => Some(Err(FieldError::UnknownChar(c))),
})
})
.flatten()
.try_collect()?;
Ok(Field {
direction: DirectionDispenser::new(),
elves,
})
}
}
impl Field {
fn has_neighbors(&self, pos: Pos2<i32>) -> bool {
for x in -1..=1 {
for y in -1..=1 {
if x != 0 || y != 0 {
let check_pos = pos + (x, y);
if self.elves.contains_key(&check_pos) {
return true;
}
}
}
}
false
}
fn check(&self, pos: Pos2<i32>, directions: DispenserIterator) -> MoveCheck {
if !self.has_neighbors(pos) {
return MoveCheck::NoNeighbors;
}
for direction in directions {
if self.is_empty(pos, direction) {
return MoveCheck::MoveTo(pos + direction, direction);
}
}
MoveCheck::NoFreeSpace
}
fn one_round(&mut self) -> bool {
let mut proposals: HashMap<Pos2<i32>, (Pos2<i32>, Direction)> = HashMap::new();
let mut deactivate = HashMap::new();
for elf in self
.elves
.iter()
.filter_map(|(elf, active)| active.then_some(*elf))
{
match self.check(elf, self.direction.iter()) {
MoveCheck::MoveTo(proposal, direction) => {
if proposals.contains_key(&proposal) {
proposals.remove(&proposal);
} else {
proposals.insert(proposal, (elf, direction));
}
}
MoveCheck::NoNeighbors => {
deactivate.insert(elf, false);
}
MoveCheck::NoFreeSpace => {}
}
}
if proposals.is_empty() {
return false;
}
self.elves.extend(deactivate.into_iter());
for (to, (from, direction)) in proposals {
self.elves.remove(&from);
self.elves.insert(to, true);
for pos in PosIterator::new(to, direction) {
if let Some(active) = self.elves.get_mut(&pos) {
*active = true;
}
}
}
self.direction.progress();
true
}
fn is_empty(&self, pos: Pos2<i32>, direction: Direction) -> bool {
PosIterator::new(pos, direction).all(|pos| !self.elves.contains_key(&pos))
}
fn forever(mut self) -> usize {
for round in 0.. {
if !self.one_round() {
return round + 1;
}
}
unreachable!()
}
fn rounds(mut self, arg: usize) -> Vec<Pos2<i32>> {
for _ in 0..arg {
self.one_round();
}
self.elves.into_keys().collect_vec()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{common::file::read_string, hashmap, hashset};
use anyhow::Result;
use std::collections::HashSet;
#[test]
fn test_part1() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(110);
let result = day.part1(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_part2() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(20);
let result = day.part2(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn parse() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example02.txt")?;
let expected = hashmap!(
Pos2::new(2, 1) => true,
Pos2::new(3, 1) => true,
Pos2::new(2, 2) => true,
Pos2::new(2, 4) => true,
Pos2::new(3, 4) => true
);
let field: Field = lines.parse()?;
assert_eq!(field.elves, expected);
assert_eq!(field.direction.0, Direction::North);
Ok(())
}
#[test]
fn round1() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example02.txt")?;
let expected = hashmap!(
Pos2::new(2, 0) => true,
Pos2::new(3, 0) => true,
Pos2::new(2, 2) => true,
Pos2::new(3, 3) => true,
Pos2::new(2, 4) => true
);
let mut field: Field = lines.parse()?;
field.one_round();
assert_eq!(field.elves, expected);
assert_eq!(field.direction.0, Direction::South);
Ok(())
}
#[test]
fn round2() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example02.txt")?;
let expected = hashset!(
Pos2::new(2, 1),
Pos2::new(3, 1),
Pos2::new(1, 2),
Pos2::new(4, 3),
Pos2::new(2, 5)
);
let field: Field = lines.parse()?;
let elves = HashSet::from_iter(field.rounds(2).into_iter());
assert_eq!(elves, expected);
Ok(())
}
}

487
src/days/day24/mod.rs Normal file
View file

@ -0,0 +1,487 @@
use super::template::{DayTrait, ResultType};
use crate::common::{direction::Direction, file::split_lines, math::lcm, pos2::Pos2};
use itertools::Itertools;
use std::{collections::HashSet, str::FromStr};
use thiserror::Error;
const DAY_NUMBER: usize = 24;
pub struct Day;
impl DayTrait for Day {
fn get_day_number(&self) -> usize {
DAY_NUMBER
}
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
let valley: Valley = lines.parse()?;
Ok(ResultType::Integer(valley.cross(Trip(1))? as i64))
}
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
let valley: Valley = lines.parse()?;
Ok(ResultType::Integer(valley.cross(Trip(3))? as i64))
}
}
#[derive(Debug, Error)]
enum BlizzardError {
#[error("Not a valid description: {0}")]
ParsingError(String),
#[error("Illegal char: {0}")]
IllegalChar(char),
#[error("Need exacly one door. Found: {0}")]
NeedExactlyOneDoor(String),
#[error("Valley has no legal shape")]
ValleyHasIllegalShape,
#[error("No valid path was found")]
NoPathFound,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Trip(usize);
impl Trip {
#[inline]
pub fn inc(&self) -> Trip {
Trip(self.0 + 1)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Time(usize);
impl Time {
#[inline]
pub fn get(&self) -> usize {
self.0
}
#[inline]
pub fn inc(&self) -> Time {
Time(self.0 + 1)
}
}
struct Valley {
height: usize,
entry: usize,
exit: usize,
storm: Storm,
}
#[derive(Debug, PartialEq, Eq)]
struct State {
time: Time,
trip: Trip,
position: Pos2<usize>,
start: Pos2<usize>,
target: Pos2<usize>,
}
impl State {
fn new(entry: Pos2<usize>, exit: Pos2<usize>) -> Self {
Self {
time: Time(0),
trip: Trip(0),
position: entry,
start: entry,
target: exit,
}
}
fn hit_target(&self) -> Self {
Self {
time: self.time.inc(),
trip: self.trip.inc(),
position: self.target,
start: self.target,
target: self.start,
}
}
fn walk_to(&self, position: Pos2<usize>) -> Self {
Self {
time: self.time.inc(),
trip: self.trip,
position,
start: self.start,
target: self.target,
}
}
fn next_states(&self, valley: &Valley) -> Vec<State> {
let frame = valley.storm.get(self.time.inc());
let mut states = Vec::with_capacity(4);
let mut dir = Direction::East;
for _ in 0..4 {
if let Some(next_pos) = self.position.check_add(dir) {
if next_pos == self.target {
states.push(self.hit_target());
} else if frame.is_accessible(&next_pos) {
states.push(self.walk_to(next_pos));
}
}
dir = dir.turn_left();
}
if frame.is_accessible(&self.position) || self.position == self.start {
states.push(self.walk_to(self.position));
}
states
}
}
impl Valley {
fn doorline(input: &str) -> Result<usize, BlizzardError> {
let doors = input
.char_indices()
.map(|(col, c)| match c {
'.' => Ok(Some(col)),
'#' => Ok(None),
_ => Err(BlizzardError::IllegalChar(c)),
})
.filter_map_ok(|item| item)
.collect::<Result<Vec<_>, _>>()?;
if doors.len() != 1 {
Err(BlizzardError::NeedExactlyOneDoor(input.to_owned()))
} else {
Ok(doors[0] - 1)
}
}
fn storm(input: &[&str]) -> Result<Blizzards, BlizzardError> {
let height = input.len();
if height == 0 {
return Err(BlizzardError::ValleyHasIllegalShape);
}
let width = input[0].len();
if width == 0
|| input[0].chars().nth(0) != Some('#')
|| input[0].chars().nth(width - 1) != Some('#')
{
return Err(BlizzardError::ValleyHasIllegalShape);
}
if !input
.iter()
.map(|row| {
(
row.len(),
row.chars().nth(0),
row.chars().nth(row.len() - 1),
)
})
.all_equal()
{
return Err(BlizzardError::ValleyHasIllegalShape);
}
let raw = input
.iter()
.enumerate()
.map(|(y, row)| {
row.char_indices().filter_map(move |(x, c)| match c {
'#' => {
if x != 0 && x != row.len() - 1 {
Some(Err(BlizzardError::IllegalChar('#')))
} else {
None
}
}
'>' => Some(Ok((Pos2::new(x - 1, y), Direction::East))),
'^' => Some(Ok((Pos2::new(x - 1, y), Direction::North))),
'<' => Some(Ok((Pos2::new(x - 1, y), Direction::West))),
'v' => Some(Ok((Pos2::new(x - 1, y), Direction::South))),
'.' => None,
_ => Some(Err(BlizzardError::IllegalChar(c))),
})
})
.flatten()
.collect::<Result<Vec<_>, _>>()?;
Ok(Blizzards::new(raw, width - 2, height))
}
fn cross(&self, trips: Trip) -> Result<usize, BlizzardError> {
let mut seen = HashSet::new();
let start = State::new(self.get_entry(), self.get_exit());
let mut queue = vec![start];
let mut next_queue = vec![];
while let Some(current) = queue.pop() {
for next_state in current.next_states(&self) {
if next_state.trip == trips {
return Ok(next_state.time.get());
}
let fingerprint = (next_state.position, next_state.trip);
if !seen.contains(&fingerprint) {
seen.insert(fingerprint);
next_queue.push(next_state);
}
}
if queue.is_empty() {
std::mem::swap(&mut queue, &mut next_queue);
seen.clear();
}
}
Err(BlizzardError::NoPathFound)
}
fn get_entry(&self) -> Pos2<usize> {
Pos2::new(self.entry, 0)
}
fn get_exit(&self) -> Pos2<usize> {
Pos2::new(self.exit, self.height + 1)
}
}
impl FromStr for Valley {
type Err = BlizzardError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let lines = split_lines(input).collect_vec();
if lines.len() < 3 {
return Err(BlizzardError::ParsingError(input.to_owned()));
}
let entry = Valley::doorline(lines[0])?;
let exit = Valley::doorline(lines[lines.len() - 1])?;
let blizzards = Valley::storm(&lines[1..lines.len() - 1])?;
let height = blizzards.height;
let storm = Storm::new(blizzards);
Ok(Self {
entry,
exit,
height,
storm,
})
}
}
#[derive(Debug, Clone)]
struct BlizzardLine {
direction: Direction,
blizzards: Vec<usize>,
len: usize,
}
impl BlizzardLine {
fn new(direction: Direction, len: usize) -> Self {
BlizzardLine {
direction,
blizzards: vec![],
len,
}
}
fn iter(&self, pos: usize, time: usize) -> impl Iterator<Item = Pos2<usize>> + '_ {
let time = time % self.len;
self.blizzards
.iter()
.map(move |delta| match self.direction {
Direction::East => Pos2::new((time + *delta) % self.len, pos),
Direction::West => Pos2::new((self.len - time + *delta) % self.len, pos),
Direction::North => Pos2::new(pos, (self.len - time + *delta) % self.len),
Direction::South => Pos2::new(pos, (time + *delta) % self.len),
})
}
fn push(&mut self, x: usize) {
self.blizzards.push(x);
}
}
struct Blizzards {
blizzards: Vec<Vec<BlizzardLine>>,
width: usize,
height: usize,
period: usize,
}
impl Blizzards {
pub fn new(raw: Vec<(Pos2<usize>, Direction)>, width: usize, height: usize) -> Self {
let mut blizzards = vec![
vec![BlizzardLine::new(Direction::East, width); height],
vec![BlizzardLine::new(Direction::North, height); width],
vec![BlizzardLine::new(Direction::West, width); height],
vec![BlizzardLine::new(Direction::South, height); width],
];
for (pos, direction) in raw {
match direction {
Direction::East | Direction::West => {
blizzards[direction as usize][pos.y()].push(pos.x())
}
Direction::North | Direction::South => {
blizzards[direction as usize][pos.x()].push(pos.y())
}
}
}
Self {
blizzards,
width,
height,
period: lcm(width, height),
}
}
pub fn frame_at_time(&self, time: usize) -> Frame {
let time = time % self.period;
let mut valley = vec![vec![true; self.width]; self.height];
for _ in 0..4 {
for dir in self.blizzards.iter() {
for (pos, blizzard) in dir.iter().enumerate() {
for item in blizzard.iter(pos, time) {
valley[item.y()][item.x()] = false;
}
}
}
}
Frame(valley)
}
}
struct Frame(Vec<Vec<bool>>);
impl Frame {
fn is_accessible(&self, pos: &Pos2<usize>) -> bool {
if pos.y() == 0 {
false
} else {
self.0
.get(pos.y() - 1)
.and_then(|row| row.get(pos.x()))
.copied()
.unwrap_or(false)
}
}
}
struct Storm(Vec<Frame>);
impl Storm {
fn new(blizzards: Blizzards) -> Self {
let storm = (0..blizzards.period)
.map(|time| blizzards.frame_at_time(time))
.collect_vec();
Self(storm)
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn get(&self, time: Time) -> &Frame {
&self.0[time.get() % self.len()]
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::common::file::read_string;
use anyhow::Result;
#[test]
fn test_part1() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(18);
let result = day.part1(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_part2() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(54);
let result = day.part2(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn parse() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example02.txt")?;
let valley: Valley = lines.parse()?;
assert_eq!(valley.entry, 0);
assert_eq!(valley.exit, 4);
assert_eq!(valley.height, 5);
assert_eq!(valley.storm.len(), 5);
assert_eq!(valley.get_entry(), Pos2::new(0, 0));
assert_eq!(valley.get_exit(), Pos2::new(4, 6));
assert_eq!(
valley.storm.get(Time(3)).is_accessible(&Pos2::new(3, 2)),
false
);
Ok(())
}
#[test]
fn parse2() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let valley: Valley = lines.parse()?;
assert_eq!(valley.entry, 0);
assert_eq!(valley.exit, 5);
assert_eq!(valley.height, 4);
assert_eq!(valley.storm.len(), 12);
assert_eq!(valley.get_entry(), Pos2::new(0, 0));
assert_eq!(valley.get_exit(), Pos2::new(5, 5));
let expected = vec![
vec![true, false, false, true, false, true],
vec![false, true, true, false, false, true],
vec![false, false, true, false, false, true],
vec![false, false, true, true, false, false],
];
assert_eq!(valley.storm.get(Time(1)).0, expected);
Ok(())
}
#[test]
fn possible_moves() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let valley: Valley = lines.parse()?;
let position = State {
time: Time(17),
trip: Trip(0),
position: Pos2::new(5, 4),
start: Pos2::new(0, 0),
target: Pos2::new(5, 5),
};
let expected = vec![
State {
time: Time(18),
trip: Trip(0),
position: Pos2::new(4, 4),
start: Pos2::new(0, 0),
target: Pos2::new(5, 5),
},
State {
time: Time(18),
trip: Trip(1),
position: Pos2::new(5, 5),
start: Pos2::new(5, 5),
target: Pos2::new(0, 0),
},
];
let next_states = position.next_states(&valley);
assert_eq!(next_states, expected);
Ok(())
}
}

141
src/days/day25/mod.rs Normal file
View file

@ -0,0 +1,141 @@
use super::template::{DayTrait, ResultType};
use crate::common::file::split_lines;
use itertools::{unfold, Itertools};
use std::iter::Sum;
use std::ops::Add;
use std::str::FromStr;
use thiserror::Error;
const DAY_NUMBER: usize = 25;
pub struct Day;
impl DayTrait for Day {
fn get_day_number(&self) -> usize {
DAY_NUMBER
}
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
let snafu: Snafu = split_lines(lines)
.map(|line| line.parse::<Snafu>())
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.sum();
Ok(ResultType::String(snafu.as_str()))
}
fn part2(&self, _lines: &str) -> anyhow::Result<ResultType> {
Ok(ResultType::Nothing)
}
}
impl Snafu {
pub fn as_str(&self) -> String {
if self.0 == 0 {
return String::from("0");
}
unfold(self.0, |value| {
if *value == 0 {
return None;
}
let m = (*value + 2) % 5;
*value = (*value + 2 - m) / 5;
Some(m)
})
.map(|digit| "=-012".chars().nth(digit).unwrap())
.collect_vec()
.into_iter()
.rev()
.join("")
}
fn new(value: i64) -> Result<Snafu, SnafuError> {
if value >= 0 {
Ok(Snafu(value as usize))
} else {
Err(SnafuError::OnlyNonNegativeSnafus(value))
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Snafu(usize);
impl Add for Snafu {
type Output = Snafu;
fn add(self, rhs: Self) -> Self::Output {
Snafu(self.0 + rhs.0)
}
}
impl Sum for Snafu {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Snafu(0), Add::add)
}
}
impl FromStr for Snafu {
type Err = SnafuError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Snafu::new(
s.chars()
.map(|c| {
"=-012"
.chars()
.position(|d| d == c)
.ok_or_else(|| SnafuError::IllegalChar(c))
})
.fold_ok(0, |acc, next| acc * 5 + (next as i64 - 2))?,
)
}
}
#[derive(Debug, Error)]
enum SnafuError {
#[error("Illegal Char: {0}")]
IllegalChar(char),
#[error("Only non negative Snafus exist. Got: {0}")]
OnlyNonNegativeSnafus(i64),
}
#[cfg(test)]
mod test {
use super::*;
use crate::common::file::read_string;
use anyhow::Result;
#[test]
fn test_part1() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = ResultType::String(String::from("2=-1=0"));
let result = day.part1(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_part2() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Nothing;
let result = day.part2(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn parse() -> Result<()> {
let input = String::from("1=-0-2");
let snafu: Snafu = input.parse()?;
assert_eq!(snafu, Snafu(1747));
assert_eq!(snafu.as_str(), input);
Ok(())
}
}

View file

@ -1,4 +1,5 @@
use super::template::{DayTrait, ResultType};
use nom::{error::Error, Err, IResult, Parser};
use thiserror::Error;
const DAY_NUMBER: usize = todo!();
@ -21,8 +22,14 @@ impl DayTrait for Day {
#[derive(Debug, Error)]
enum DayError {
#[error("Dummy")]
Dummy,
#[error("Not a valid description: {0}")]
ParsingError(String),
}
impl From<Err<Error<&str>>> for DayError {
fn from(error: Err<Error<&str>>) -> Self {
DayError::ParsingError(error.to_string())
}
}
#[cfg(test)]

View file

@ -18,6 +18,11 @@ mod day17;
mod day18;
mod day19;
mod day20;
mod day21;
mod day22;
mod day23;
mod day24;
mod day25;
mod template;
pub use template::DayTrait;
@ -27,7 +32,7 @@ pub mod day_provider {
use super::*;
use thiserror::Error;
const MAX_DAY: usize = 20;
const MAX_DAY: usize = 25;
pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> {
match day_num {
@ -51,6 +56,11 @@ pub mod day_provider {
18 => Ok(Box::new(day18::Day)),
19 => Ok(Box::new(day19::Day)),
20 => Ok(Box::new(day20::Day)),
21 => Ok(Box::new(day21::Day)),
22 => Ok(Box::new(day22::Day)),
23 => Ok(Box::new(day23::Day)),
24 => Ok(Box::new(day24::Day)),
25 => Ok(Box::new(day25::Day)),
_ => Err(ProviderError::InvalidNumber(day_num)),
}
}

View file

@ -1,5 +1,3 @@
#![feature(get_many_mut)]
mod common;
mod days;
mod macros;