Skip to content

Commit

Permalink
Day_03(2023): solved
Browse files Browse the repository at this point in the history
  • Loading branch information
Sheinxy committed Dec 3, 2023
1 parent e6040c5 commit e5a4ef1
Show file tree
Hide file tree
Showing 5 changed files with 348 additions and 1 deletion.
52 changes: 52 additions & 0 deletions 2023/Day_03/Day_03.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module Main where

import Data.Char
import Data.List
import Data.Matrix (Matrix, fromLists, (!), nrows, ncols)
import System.Environment

data Number = Number { value :: Int, row :: Int, span :: (Int, Int) } deriving (Show)

-- Grid, Numbers, Potential Gears
type Input = (Matrix Char, [Number], [(Int, Int)])
type Output = Int

parseInput :: String -> Input
parseInput input = (grid, numbers, gears)
where inLines = lines input
grid = fromLists inLines
-- Getting the list of numbers
numCol = map (zip [1 .. ]) inLines
grouped = map (groupBy (\a b -> isDigit (snd a) == isDigit (snd b))) numCol
cleaned = map (map (\l -> ((fst $ head l, fst $ last l), map snd l))) grouped
numRow = zip [1 .. ] $ map (filter (\(_, n) -> all isDigit n)) cleaned
numbers = concatMap (\(r, row) -> map (\(s, n) -> Number (read n) r s) row) numRow
-- Getting the list of gears
gearCols = map (filter ((== '*') . snd )) numCol
gearRows = zip [1 .. ] gearCols
gears = concatMap (\(r, row) -> map (\(c, _) -> (r, c)) row) gearRows

partOne :: Input -> Output
partOne (grid, numbers, _) = sum . map (value) . filter isAdjacent $ numbers
where maxRow = nrows grid
maxCol = nrows grid
getNeighbours (Number _ r (c1, c2)) = map (grid !) [(i, j) | i <- [r - 1 .. r + 1], j <- [c1 - 1 .. c2 + 1], 0 < i && i <= maxRow, 0 < j && j <= maxCol]
isAdjacent = any (\c -> not (isDigit c) && c /= '.') . getNeighbours

partTwo :: Input -> Output
partTwo (grid, numbers, gears) = sum . map (product . map value) . filter ((== 2) . length) . map getNeighbours $ gears
where maxRow = nrows grid
maxCol = nrows grid
getNeighboursPos (r, c) = [(i, j) | i <- [r - 1 .. r + 1], j <- [c - 1 .. c + 1], 0 < i && i <= maxRow, 0 < j && j <= maxCol]
getNeighbours g = filter (\(Number _ r (c1, c2)) -> any (`elem` neighbours) [(r, c) | c <- [c1 .. c2]]) numbers where neighbours = getNeighboursPos g

compute :: Input -> String -> IO ()
compute input "parse" = print input
compute input "one" = print . partOne $ input
compute input "two" = print . partTwo $ input
compute input _ = error "Unknown part"

main = do
args <- getArgs
input <- parseInput <$> (readFile $ last args)
mapM (compute input) $ init args
140 changes: 140 additions & 0 deletions 2023/Day_03/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
## Day 03

It is already time for 2D array manipulation in Haskell! 😿

```hs
import Data.Char
import Data.List
import Data.Matrix (Matrix, fromLists, (!), nrows, ncols)
import System.Environment

data Number = Number { value :: Int, row :: Int, span :: (Int, Int) } deriving (Show)

-- Grid, Numbers, Potential Gears
type Input = (Matrix Char, [Number], [(Int, Int)])
type Output = Int

parseInput :: String -> Input
parseInput input = (grid, numbers, gears)
where inLines = lines input
grid = fromLists inLines
-- Getting the list of numbers
numCol = map (zip [1 .. ]) inLines
grouped = map (groupBy (\a b -> isDigit (snd a) == isDigit (snd b))) numCol
cleaned = map (map (\l -> ((fst $ head l, fst $ last l), map snd l))) grouped
numRow = zip [1 .. ] $ map (filter (\(_, n) -> all isDigit n)) cleaned
numbers = concatMap (\(r, row) -> map (\(s, n) -> Number (read n) r s) row) numRow
-- Getting the list of gears
gearCols = map (filter ((== '*') . snd )) numCol
gearRows = zip [1 .. ] gearCols
gears = concatMap (\(r, row) -> map (\(c, _) -> (r, c)) row) gearRows

partOne :: Input -> Output
partOne (grid, numbers, _) = sum . map (value) . filter isAdjacent $ numbers
where maxRow = nrows grid
maxCol = nrows grid
getNeighbours (Number _ r (c1, c2)) = map (grid !) [(i, j) | i <- [r - 1 .. r + 1], j <- [c1 - 1 .. c2 + 1], 0 < i && i <= maxRow, 0 < j && j <= maxCol]
isAdjacent = any (\c -> not (isDigit c) && c /= '.') . getNeighbours

partTwo :: Input -> Output
partTwo (grid, numbers, gears) = sum . map (product . map value) . filter ((== 2) . length) . map getNeighbours $ gears
where maxRow = nrows grid
maxCol = nrows grid
getNeighboursPos (r, c) = [(i, j) | i <- [r - 1 .. r + 1], j <- [c - 1 .. c + 1], 0 < i && i <= maxRow, 0 < j && j <= maxCol]
getNeighbours g = filter (\(Number _ r (c1, c2)) -> any (`elem` neighbours) [(r, c) | c <- [c1 .. c2]]) numbers where neighbours = getNeighboursPos g
```

There is a lot to unpack here so let's start by the input:
```hs
-- Grid, Numbers, Potential Gears
type Input = (Matrix Char, [Number], [(Int, Int)])
```
There are three interesting parts here: the actual grid (basically the input, almost raw, just wrapped inside a Data.Matrix),
the list of numbers and the list of potential grids represented by their coordinates.

Numbers are represented with a data structure containing their value, their row, and their span (first column and last column)

Parsing the input is hellish:
```hs
parseInput :: String -> Input
parseInput input = (grid, numbers, gears)
where inLines = lines input
grid = fromLists inLines
-- Getting the list of numbers
numCol = map (zip [1 .. ]) inLines
grouped = map (groupBy (\a b -> isDigit (snd a) == isDigit (snd b))) numCol
cleaned = map (map (\l -> ((fst $ head l, fst $ last l), map snd l))) grouped
numRow = zip [1 .. ] $ map (filter (\(_, n) -> all isDigit n)) cleaned
numbers = concatMap (\(r, row) -> map (\(s, n) -> Number (read n) r s) row) numRow
-- Getting the list of gears
gearCols = map (filter ((== '*') . snd )) numCol
gearRows = zip [1 .. ] gearCols
gears = concatMap (\(r, row) -> map (\(c, _) -> (r, c)) row) gearRows
```

There are three main parts:
```hs
where inLines = lines input
grid = fromLists inLines
```
This is the easy part: get the lines from the input, and transform it into a matrix

Now for the hard part:
```hs
-- Getting the list of numbers
numCol = map (zip [1 .. ]) inLines
grouped = map (groupBy (\a b -> isDigit (snd a) == isDigit (snd b))) numCol
cleaned = map (map (\l -> ((fst $ head l, fst $ last l), map snd l))) grouped
numRow = zip [1 .. ] $ map (filter (\(_, n) -> all isDigit n)) cleaned
numbers = concatMap (\(r, row) -> map (\(s, n) -> Number (read n) r s) row) numRow
```
I start by labelling each column in each row, then I group every number digit together (so I get the numbers whole in the for [(col1, digit 1), (col2, digit2) ...]).

After that, I clean things around by taking only the first and last column for each digit, and putting the digits together in a list: ((col1, colLast), [digit1, digit2, ...])

Now that I have that, I only keep the actual numbers by filtering on the digits to make sure they're ACTUAL digits, and I now label the rows.

Finally, for each row I transform every (span, digits) into a number data structure, and I flatten my grid into a single list.


Now the second hardest part:
```hs
-- Getting the list of gears
gearCols = map (filter ((== '*') . snd )) numCol
gearRows = zip [1 .. ] gearCols
gears = concatMap (\(r, row) -> map (\(c, _) -> (r, c)) row) gearRows
```
First I keep the * from the labeled column list, next I label the rows, and finally I transform these into coordinates.


This was the hard part (the list of gears was done after I did part one btw)

Now for the puzzles themselves:

```hs
partOne :: Input -> Output
partOne (grid, numbers, _) = sum . map (value) . filter isAdjacent $ numbers
where maxRow = nrows grid
maxCol = nrows grid
getNeighbours (Number _ r (c1, c2)) = map (grid !) [(i, j) | i <- [r - 1 .. r + 1], j <- [c1 - 1 .. c2 + 1], 0 < i && i <= maxRow, 0 < j && j <= maxCol]
isAdjacent = any (\c -> not (isDigit c) && c /= '.') . getNeighbours
```
I use a function getNeighbours that, for a given number, generates the list of possible neighbour coordinates and maps this list to the values in the frid. Then I simply need to check if any of these values is a symbol (ie not . nor number) to know that this number is adjacent to one.

I filter the numbers that are adjacent to a symbol, get their values and sum them together!

Part two is similar:
```hs
partTwo :: Input -> Output
partTwo (grid, numbers, gears) = sum . map (product . map value) . filter ((== 2) . length) . map getNeighbours $ gears
where maxRow = nrows grid
maxCol = nrows grid
getNeighboursPos (r, c) = [(i, j) | i <- [r - 1 .. r + 1], j <- [c - 1 .. c + 1], 0 < i && i <= maxRow, 0 < j && j <= maxCol]
getNeighbours g = filter (\(Number _ r (c1, c2)) -> any (`elem` neighbours) [(r, c) | c <- [c1 .. c2]]) numbers where neighbours = getNeighboursPos g
```
This time, I first generate the list of neighbouring coordinates, and the I filter the number list by checking if any matches these coordinates.

Then for each gear I keep those with exactly two neighbours, I get the value of these neighbours and multiply them, and I sum all the results.


I am not happy with that 😿
Loading

0 comments on commit e5a4ef1

Please sign in to comment.