{-# Language QuasiQuotes, ImportQualifiedPost #-}
{-|
Module      : Main
Description : Day 15 solution
Copyright   : (c) Eric Mertens, 2023
License     : ISC
Maintainer  : emertens@gmail.com

<https://adventofcode.com/2023/day/15>

This problem has us follow a lens update sequence. The solution
below stores the lenses in an array and uses accumArray to
efficiently apply updates to that array in-place.

>>> :main + "rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7\n"
1320
145

-}
module Main (main) where

import Advent (format)
import Data.Array (accumArray, assocs)
import Data.Char (ord)

-- | Parse the input sequence and print the results of both parts.
--
-- >>> :main
-- 503487
-- 261505
main :: IO ()
IO ()
main =
 do input <- [format|2023 15 (%a+(-|=%d))!&,%n|]
    print (sum [hash raw | (raw, _) <- input])

    let boxes = ([(String, Int)] -> (String, Maybe Int) -> [(String, Int)])
-> [(String, Int)]
-> (Int, Int)
-> [(Int, (String, Maybe Int))]
-> Array Int [(String, Int)]
forall i e a.
Ix i =>
(e -> a -> e) -> e -> (i, i) -> [(i, a)] -> Array i e
accumArray [(String, Int)] -> (String, Maybe Int) -> [(String, Int)]
updateBox [] (Int
0, Int
255)
                  [(String -> Int
hash String
lbl, (String
lbl, Maybe Int
cmd)) | (String
_, (String
lbl, Maybe Int
cmd)) <- [(String, (String, Maybe Int))]
input]

    print (sum [ (1 + box) * sum (zipWith (*) [1..] (map snd xs))
               | (box, xs) <- assocs boxes])

-- | Run the HASH algorithm on an input string.
hash :: String -> Int
hash :: String -> Int
hash String
str = (Int -> Char -> Int) -> Int -> String -> Int
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (\Int
acc Char
x -> Int
17 Int -> Int -> Int
forall a. Num a => a -> a -> a
* (Char -> Int
ord Char
x Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
acc)) Int
0 String
str Int -> Int -> Int
forall a. Integral a => a -> a -> a
`rem` Int
256

-- | Either update the focal length or remove a lens by label of a lens box.
updateBox ::
  [(String, Int)]     {- ^ lens box                -} ->
  (String, Maybe Int) {- ^ label, new focal length -} ->
  [(String, Int)]     {- ^ updated lens box        -}
updateBox :: [(String, Int)] -> (String, Maybe Int) -> [(String, Int)]
updateBox [(String, Int)]
prev (String
lbl, Maybe Int
Nothing) = ((String, Int) -> Bool) -> [(String, Int)] -> [(String, Int)]
forall a. (a -> Bool) -> [a] -> [a]
filter ((String
lbl String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/=) (String -> Bool)
-> ((String, Int) -> String) -> (String, Int) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String, Int) -> String
forall a b. (a, b) -> a
fst) [(String, Int)]
prev
updateBox [(String, Int)]
prev (String
lbl, Just Int
n ) = [(String, Int)] -> [(String, Int)]
go [(String, Int)]
prev
  where
    go :: [(String, Int)] -> [(String, Int)]
go ((String
k, Int
_) : [(String, Int)]
xs) | String
lbl String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
k = (String
lbl, Int
n) (String, Int) -> [(String, Int)] -> [(String, Int)]
forall a. a -> [a] -> [a]
: [(String, Int)]
xs
    go ((String, Int)
x      : [(String, Int)]
xs)            = (String, Int)
x (String, Int) -> [(String, Int)] -> [(String, Int)]
forall a. a -> [a] -> [a]
: [(String, Int)] -> [(String, Int)]
go [(String, Int)]
xs
    go []                       = [(String
lbl, Int
n)]