Advent of Code 2023 day 15

    Day 15 looked like it would be another day with just basic list processing, but that wasn't to be.

    Part 1

    This was simply the implementation of the hashing function. I split the input string on commas, then apply the hash to each element. No data structures or parsing required.

    part1 :: Text -> Int
    part1 = sum . fmap (hash . unpack) . splitOn ","
    
    hash :: String -> Int
    hash s = foldl' go 0 s
      where go current c = ((current + ord c) * 17)  `mod` 256

    Part 2

    This is where I need some data structures. I defined an Instruction, a Lens, and a Facility (the collection of boxes).

    data Instruction = Remove String | Insert String Int deriving (Show, Eq)
    data Lens = Lens {lensLabel :: String, lensPower :: Int} deriving (Show, Eq)
    type Facility = M.IntMap [Lens]

    Parsing instructions is uncomplicated.

    instructionsP = instructionP `sepBy` ","
    instructionP = removeP <|> insertP
    
    removeP = Remove <$> (many1 letter) <* "-"
    insertP = Insert <$> ((many1 letter) <* "=") <*> decimal

    The key here is processing an instruction to adjust the facility.

    Removing a lens takes the existing lenses in a box and removes all that match the label in the instruction.

    Inserting a lens has two cases. If any lens with this label already exists in the given box, replace it with the new one. Otherwise, add the new lens to the end.

    process :: Facility -> Instruction -> Facility
    process facility (Remove s) = 
      M.adjust (filter ((/= s) . lensLabel))
               (hash s) 
               facility
    process facility (Insert s p)
      | any ((== s) . lensLabel) content = M.insert hs content' facility
      | otherwise = M.insert hs (content ++ [lens]) facility
      where hs = hash s
            content = M.findWithDefault [] hs facility
            lens = Lens s p
            content' = fmap replaceLens content
            replaceLens l 
              | lensLabel l == s = lens
              | otherwise = l

    Finding the power of the faciltiy is the sum of all the powers of a box. Finding the power of a box uses zipWith to do the product of lens and position, then multiplies the whole thing with the box number.

    powerCell :: Int -> [Lens] -> Int
    powerCell boxNum lenses = 
      (boxNum + 1) * (sum $ zipWith (*) [1..] (fmap lensPower lenses))
    
    power :: Facility -> Int 
    power = sum . M.elems . M.mapWithKey powerCell

    The solution is finding the power after processing the instructions.

    part2 :: [Instruction] -> Int
    part2 = power . processAll

    Code

    You can get the code from my locally-hosted Git repo, or from Gitlab.

    Neil Smith

    Read more posts by this author.