Skip to content

Commit 2201309

Browse files
committed
merge 2022 22_alt into 22
1 parent c0d950e commit 2201309

File tree

5 files changed

+158
-241
lines changed

5 files changed

+158
-241
lines changed

common/src/Advent/Coord.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ drawCoords coords = drawPicture (Map.fromList [(c,'█') | c <- toList coords])
166166

167167
-- | Given a list of lines pair up each character with
168168
-- its position.
169-
coordLines :: [String] -> [(Coord, Char)]
169+
coordLines :: [[a]] -> [(Coord, a)]
170170
coordLines rows = [(C y x, z) | (y,row) <- zip [0..] rows, (x,z) <- zip [0..] row]
171171

172172
-- | Apply a function to the y and x coordinate

common/src/Advent/Format/Enum.hs

+1
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,5 @@ symbolNames =
6969
, ("COMMA", ',')
7070
, ("PLUS", '+')
7171
, ("TILDE", '~')
72+
, ("SPACE", ' ')
7273
]

solutions/solutions.cabal

-5
Original file line numberDiff line numberDiff line change
@@ -974,11 +974,6 @@ executable sln_2022_22
974974
main-is: 2022/22.hs
975975
build-depends: containers
976976

977-
executable sln_2022_22_alt
978-
import: day
979-
main-is: 2022/22_alt.hs
980-
build-depends: containers
981-
982977
executable sln_2022_23
983978
import: day
984979
main-is: 2022/23.hs

solutions/src/2022/22.hs

+156-92
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,181 @@
1-
{-# Language QuasiQuotes, TemplateHaskell, ImportQualifiedPost, LambdaCase, BangPatterns #-}
1+
{-# Language QuasiQuotes, BangPatterns, ConstraintKinds, TemplateHaskell, ImportQualifiedPost, LambdaCase, ImplicitParams, DataKinds #-}
22
{-|
33
Module : Main
44
Description : Day 22 solution
5-
Copyright : (c) Eric Mertens, 2022
5+
Copyright : (c) Eric Mertens, 2024
66
License : ISC
77
Maintainer : [email protected]
88
99
<https://adventofcode.com/2022/day/22>
1010
11+
This solution works by first exploring the input file and assigning a cube
12+
location to each flattened location. The path is explored in terms of the cube
13+
coordinates and then is converted back into input file coordinates at the end.
14+
15+
>>> :{
16+
:main + " ...#
17+
.#..
18+
#...
19+
....
20+
...#.......#
21+
........#...
22+
..#....#....
23+
..........#.
24+
...#....
25+
.....#..
26+
.#......
27+
......#.
28+
\&
29+
10R5L5R10L4R5L5
30+
"
31+
:}
32+
6032
33+
5031
34+
1135
-}
12-
module Main where
36+
module Main (main) where
1337

38+
import Advent (stageTH, format, countBy)
39+
import Advent.Coord (Coord(..), coordLines, above, below, left, origin, right, east, turnLeft, turnRight)
40+
import Advent.Permutation (Permutation, mkPermutation, invert)
41+
import Advent.Search (dfsOn)
1442
import Data.Map (Map)
1543
import Data.Map qualified as Map
16-
import Data.List (foldl')
17-
18-
import Advent (format, stageTH)
19-
import Advent.Coord
44+
import Data.Set (Set)
45+
import Data.Set qualified as Set
2046

47+
-- | Left and right turns
2148
data D = DL | DR
2249

50+
data C = C_HASH | C_DOT deriving (Eq)
51+
2352
stageTH
2453

54+
-- | Constraint for upper bound of cube coordinates
55+
type HiVal = ?hiVal :: Int
56+
2557
-- |
2658
-- >>> :main
2759
-- 162186
2860
-- 55267
2961
main :: IO ()
3062
main =
31-
do (rawmap, path) <- [format|2022 22 (( |.|#)*!%n)*%n(%u|@D)*%n|]
32-
let board = Map.filter (' ' /=) (Map.fromList (coordLines rawmap))
33-
let start = minimum (Map.keys board)
34-
print (score (go1 path start board))
35-
print (score (go2 path start board))
36-
37-
score :: (Coord, Coord) -> Int
38-
score (C y x, dir) = 1000 * (y+1) + 4 * (x+1) + faceval
39-
where
40-
faceval
41-
| dir == east = 0
42-
| dir == south = 1
43-
| dir == west = 2
44-
| dir == north = 3
45-
| otherwise = error "faceval: bad direction"
46-
47-
go1 :: [Either Int D] -> Coord -> Map Coord Char -> (Coord, Coord)
48-
go1 commands start board = foldl' f (start, east) commands
49-
where
50-
f (!here, !dir) = \case
51-
Left n -> (walk1 n dir here board, dir)
52-
Right DL -> (here, turnLeft dir)
53-
Right DR -> (here, turnRight dir)
54-
55-
walk1 :: Int -> Coord -> Coord -> Map Coord Char -> Coord
56-
walk1 0 _ here _ = here
57-
walk1 n dir here board
58-
| board Map.! here' == '#' = here
59-
| otherwise = walk1 (n-1) dir here' board
60-
where
61-
here'
62-
| Map.member (here+dir) board = here+dir
63-
| otherwise = last (takeWhile (`Map.member` board) (iterate (subtract dir) here))
64-
65-
go2 :: [Either Int D] -> Coord -> Map Coord Char -> (Coord, Coord)
66-
go2 commands start board = foldl' f (start, east) commands
63+
do (rawMaze, cmds) <- [format|2022 22 (( |@C)*%n)*%n(%u|@D)*%n|]
64+
let maze = parseMaze rawMaze
65+
print (part1 maze cmds)
66+
print (part2 maze cmds)
67+
68+
-- | Build a coordinate map of the maze
69+
parseMaze :: [[Maybe C]] -> Map Coord C
70+
parseMaze cs = Map.fromList [(c, x) | (c, Just x) <- coordLines cs]
71+
72+
-- | The password is determined from the ending coordinate and direction.
73+
password :: Coord -> Int -> Int
74+
password (C y x) z = 1000 * (y + 1) + 4 * (x + 1) + z
75+
76+
-- | Follow the command sequence while using a simple wrap-around
77+
-- logic to compute the password.
78+
part1 :: Map Coord C -> [Either Int D] -> Int
79+
part1 maze cmds = password end (d `mod` 4)
6780
where
68-
f (!here, !dir) = \case
69-
Left n -> walk2 n dir here board
70-
Right DL -> (here, turnLeft dir)
71-
Right DR -> (here, turnRight dir)
72-
73-
walk2 :: Int -> Coord -> Coord -> Map Coord Char -> (Coord, Coord)
74-
walk2 0 dir here _ = (here,dir)
75-
walk2 n dir here board
76-
| board Map.! here' == '#' = (here,dir)
77-
| otherwise = walk2 (n-1) dir' here' board
81+
(start, _) = Map.findMin maze
82+
(end, _, d) = foldl (applyCommand1 maze) (start, east, 0) cmds
83+
84+
applyCommand1 :: Map Coord C -> (Coord, Coord, Int) -> Either Int D -> (Coord, Coord, Int)
85+
applyCommand1 board (!here, !dir, !facing) = \case
86+
Right DL -> (here, turnLeft dir, facing - 1)
87+
Right DR -> (here, turnRight dir, facing + 1)
88+
Left n -> (here', dir, facing)
89+
where
90+
here' = last (takeWhile isOpen (take (n + 1) (iterate step here)))
91+
isOpen x = board Map.! x == C_DOT
92+
step x
93+
| let x' = x + dir, Map.member x' board = x'
94+
| otherwise = last (takeWhile (`Map.member` board) (iterate (subtract dir) x))
95+
96+
-- | Follow the command sequence while treating the maze as a cube net
97+
-- to compute the resulting password.
98+
part2 :: Map Coord C -> [Either Int D] -> Int
99+
part2 maze cmds =
100+
do -- figure out the side-length of the cube we're working with
101+
-- so that we can handle both examples and regular inputs
102+
let ?hiVal = until (\x -> 6 * x * x >= length maze) (1 +) 1 - 1
103+
104+
-- associate cube coordinates with all of the input file coordinates
105+
let cube = buildCube (Map.keysSet (Map.filter (C_DOT ==) maze))
106+
107+
-- figure out the cube coordinate that our path ends on
108+
let (end, facing) = cube Map.! foldl (flip (applyCommand2 cube)) locOrigin cmds
109+
110+
-- compute the "password" from the end location
111+
password end facing
112+
113+
-- | Given the set of flat path coordinates compute the cube-coordinate
114+
-- to flat coordinate and facing map.
115+
buildCube :: HiVal => Set Coord -> Map Loc (Coord, Int)
116+
buildCube input = Map.fromList
117+
[(li, (c, i)) | (l, c) <- dfsOn snd step (locOrigin, Set.findMin input)
118+
, (li, i) <- zip (iterate locRotateL l) [0..3]]
78119
where
79-
(here', dir') =
80-
let fr = coordRow here `mod` 50
81-
fc = coordCol here `mod` 50
82-
fr' = 49 - fr in
83-
case (cubeface here, cubeface (here+dir)) of
84-
(_,y) | -1 /= y -> (here+dir, dir)
85-
86-
(1,_) | dir == north -> (C (150 + fc ) 0,east)
87-
(1,_) | dir == west -> (C (100 + fr') 0, east)
88-
89-
(2,_) | dir == north -> (C 199 fc, north)
90-
(2,_) | dir == east -> (C (100 + fr') 99, west)
91-
(2,_) | dir == south -> (C ( 50 + fc ) 99, west)
92-
93-
(3,_) | dir == east -> (C 49 (100 + fr), north)
94-
(3,_) | dir == west -> (C 100 fr , south)
95-
96-
(4,_) | dir == east -> (C fr' 149, west)
97-
(4,_) | dir == south -> (C (150 + fc) 49, west)
98-
99-
(5,_) | dir == north -> (C (50 + fc) 50, east)
100-
(5,_) | dir == west -> (C fr' 50, east)
101-
102-
(6,_) | dir == east -> (C 149 ( 50 + fr), north)
103-
(6,_) | dir == south -> (C 0 (100 + fc), south)
104-
(6,_) | dir == west -> (C 0 ( 50 + fr), south)
105-
106-
(a,b) -> error (show (a,b, dir))
107-
108-
cubeface :: Coord -> Int
109-
cubeface (C y x) =
110-
case (div y 50, div x 50) of
111-
(0,1) -> 1
112-
(0,2) -> 2
113-
(1,1) -> 3
114-
(2,0) -> 5
115-
(2,1) -> 4
116-
(3,0) -> 6
117-
_ -> -1
120+
step (l, c) =
121+
[(locRight l, right c) | right c `Set.member` input] ++
122+
[(locLeft l, left c) | left c `Set.member` input] ++
123+
[(locUp l, above c) | above c `Set.member` input] ++
124+
[(locDown l, below c) | below c `Set.member` input]
125+
126+
-- | Apply a command to the state of the walker on the cube.
127+
-- Each move is either forward a certain number or a turn.
128+
applyCommand2 :: HiVal => Map Loc a -> Either Int D -> Loc -> Loc
129+
applyCommand2 maze = \case
130+
Left n -> last . takeWhile (`Map.member` maze) . take (n + 1) . iterate locRight
131+
Right DL -> locRotateR
132+
Right DR -> locRotateL
133+
134+
-- | Symmetric group S4 corresponds to the symmetries of a cube.
135+
--
136+
-- This cube's diagonals are labeled and the face is read off the
137+
-- top clockwise. Rotations about an axis use left-hand rule.
138+
--
139+
-- @
140+
-- 0--1 z
141+
-- /| /| |
142+
-- 3--2 | o-x
143+
-- | 2|-3 /
144+
-- |/ |/ y
145+
-- 1--0
146+
-- @
147+
type S4 = Permutation 4
148+
149+
rotX, rotY, rotZ :: S4
150+
rotX = mkPermutation ([3,2,0,1] !!)
151+
rotY = mkPermutation ([2,0,3,1] !!)
152+
rotZ = mkPermutation ([3,0,1,2] !!)
153+
154+
-- | A pair a rotation of a cube face and a position on that face.
155+
data Loc = Loc S4 Coord
156+
deriving (Show, Ord, Eq)
157+
158+
-- | Initial location on the top-left of a face.
159+
locOrigin :: Loc
160+
locOrigin = Loc mempty origin
161+
162+
locRight, locLeft, locUp, locDown, locRotateL, locRotateR :: HiVal => Loc -> Loc
163+
locRight (Loc p (C y x))
164+
| x < ?hiVal = Loc p (C y (x + 1))
165+
| otherwise = Loc (p <> invert rotY) (C y 0)
166+
167+
locLeft (Loc p (C y x))
168+
| 0 < x = Loc p (C y (x - 1))
169+
| otherwise = Loc (p <> rotY) (C y ?hiVal)
170+
171+
locDown (Loc p (C y x))
172+
| y < ?hiVal = Loc p (C (y + 1) x)
173+
| otherwise = Loc (p <> rotX) (C 0 x)
174+
175+
locUp (Loc p (C y x))
176+
| 0 < y = Loc p (C (y - 1) x)
177+
| otherwise = Loc (p <> invert rotX) (C ?hiVal x)
178+
179+
locRotateR (Loc p (C y x)) = Loc (p <> rotZ) (C x (?hiVal - y))
180+
181+
locRotateL (Loc p (C y x)) = Loc (p <> invert rotZ) (C (?hiVal - x) y)

0 commit comments

Comments
 (0)