AOC23 - 2 December

2023-12-2

Today is Saturday, however I have many fun plans with my girlfriend. So this post is going to be a short one, and I will only focus on the essentials. Let's get to it.

Day 2 of Advent of Code 2023

Pasted image 20231202105120.png

So let's copy my One folder and rename to Two, fire up elm reactor and ponder. I have a hunch that this is solvable by statistics. But maybe there is a smarter way I can find, once I modeled the data. Today I will begin with that. Everything else stays the same.

(but before actually doing any of the data modeling, I will first add a bit of styling to improve the user experience a notch)

view : Model -> Html Msg

view model =
  div [style "display" "flex", style "flex-direction" "column", style "align-items" "center", style "margin" "40px"]
  [ textarea
      [style "width" "600px", style "height" "400px"
      ]
      []
  , p [] [text ""]
  ]

Now my prompt area is centered and has some room to breath.

So, we have cubes.

  • Cubes can be Red, Green and Blue.
  • A bag gets loaded with a certain amount of cubes (uknown to me at the time)
  • He will grab a few cubes and show them to me
  • Resulting in a comma separated set of results
  • Each set being separated by semicolons
type Cube = Red | Blue | Green

type alias CubeSet = Dict Cube Int

type alias Game = List CubeSet

The beauty of Data modeling in a Type strong functional language, is that you can precisely model the domain in a clear and concise way, no code line is wasted on any additional constructs. Also CubeCount is an alias for a tuple of Cubes and their amount, since I can't use my own type as key for a Dict. CubeSet defined like this is also abstract. It can both represent the entire Set of cubes loaded into the game, aswell as the sequence of Picks from the Elf inside one game.

To implement a custom type dictionary I had to use a library, which changes the way we update our set:

...
import AssocList as Dict exposing (Dict)
...

update : k -> (Maybe v -> Maybe v) -> Dict k v -> Dict k v

Now we need to parse the input.

A UI reflecting the problem

But first let me read the challenge again. Ok so we have a long list of games as main input.
We then need a way to input the Set of Cubes available. I will make a counter for each color.

...

type Msg = OnIncrement Cube | OnDecrement Cube | OnPromptUpdate String

type alias Model =

  {loadedCubes : CubeSet
  , gameList : String
  }

...

init : Model
init =
  { loadedCubes = Dict.fromList [(Red, 0), (Green, 0), (Blue, 0)]
  , gameList = ""
  }

view : Model -> Html Msg
view model =
  div [style "display" "flex", style "flex-direction" "column", style "align-items" "center", style "margin" "40px"]
  [ textarea
      [style "width" "600px", style "height" "400px"
      , onInput OnPromptUpdate
      ]
      [text model.gameList]
  , div [style "display" "flex", style "flex-direction" "row"]
      [cubeCounter model Red
      , cubeCounter model Blue
      , cubeCounter model Green
      ]
  , p [] [text ""]
  ]

writeColor : Cube -> String
writeColor color =

  case color of
    Red -> "RED"
    Blue -> "BLUE"
    Green -> "GREEN" 

readColor : String -> Maybe Cube
readColor string =

  case string of
    "red" -> Just Red
    "green" -> Just Green
    "blue" -> Just Blue
    _ -> Nothing

cubeCounter : Model -> Cube -> Html Msg
cubeCounter model color =

  div []
  [ div [style "display" "flex", style "flex-direction" "column", style "margin" "20px"]
    [ button [onClick <| OnIncrement color] [text "+"]
    , div [style "margin" "20px"] [text (writeColor color ++ " :" ++ (String.fromInt <| getCountForColor model.loadedCubes color))]
    , button [onClick <| OnDecrement color] [text "-"]
    ]
  ]

getCountForColor : CubeSet -> Cube -> Int
getCountForColor set color = (Maybe.withDefault 0 <| Dict.get color set)

update : Msg -> Model -> Model
update msg model =

  case msg of
    OnDecrement color -> changeCubeCount color False model
    OnIncrement color -> changeCubeCount color True model
    OnPromptUpdate a -> {model | gameList = a}

changeCubeCount : Cube -> Bool -> Model -> Model
changeCubeCount color up model = 

	{model | loadedCubes =
	  Dict.update color
		(\a -> Just ( Maybe.withDefault 0 a + (if up then 1 else -1)))
		model.loadedCubes
	}

Pasted image 20231202123100.png

Having an updated UI reflecting my Problem, I am off to have a christmassy day with girlfriend and friends.

To maybe hell and back

I am back. It is late, and I didn't really have the time or right headspace to reflect anything. I started by parsing the input and started to lose myself in a messy Maybe hell (which I am sure could be resolved more elegantly), but certainly not at 1am.

processCode : Model -> String -> Int
processCode model change =

  String.lines change |>
  List.map parseGame |>
  (List.filter (possibleGame model.loadedCubes)) |>
  (List.map .id) |>
  List.sum

  

possibleGame : CubeSet -> Game -> Bool
possibleGame cubeset game = True

parseGame : String -> Game
parseGame string =

  let

    queryGameId =
      Maybe.withDefault Regex.never <|
      Regex.fromString "(?<=Game\\s)\\d*"

    gameSetList = Array.get 1 (Array.fromList <| String.split ":" string)
    matchedId = Maybe.withDefault

            { match = ""
            , index = 0
            , number = 0
            , submatches = []
            } <| List.head <| Debug.log "Regex match: " Regex.find queryGameId string

  in
   { sets = List.map parseSetList (String.split ";" <| Maybe.withDefault "" gameSetList)
    , id = matchedId.match |> String.toInt >> Maybe.withDefault 0
    }

  

parseSetList : String -> CubeSet

parseSetList string = Dict.fromList <|

  (String.split "," string |> List.map ((String.dropLeft 1) >> resolveCubeAmount) |> Maybe.Extra.values)

  

resolveCubeAmount : String -> Maybe (Cube, Int)
resolveCubeAmount string =

  let

    values = Array.fromList <| String.split " " string
    result = (readColor (Maybe.withDefault "" <| Array.get 1 values), String.toInt <| Maybe.withDefault "" <| Array.get 0 values)
  in traverseMaybe result

  

traverseMaybe : (Maybe a, Maybe b) -> Maybe (a, b)
traverseMaybe tuple =

  case tuple of

    (Just a, Just b) -> Just (a, b)
    (Nothing, _) -> Nothing
    (_, Nothing) -> Nothing

Essentially what I did, is similar to day 1, parsed the text to lines, mapped over them with parseGame, filter for possibleGames, get the id of each game, and sum the ids. Game parse is a matter of matching "Game xx:" which can be done easily with a positive lookbehind and then with multiple string splitting I could dissect ";" into sets with each entry being separated by ",". The rest is Maybe handling and String cleanup. The result is a Game with an Id and a list of cube sets.

Now to the big question:

Is the game Possible?

Now initially I thought I had to do a statistical evaluation of the likelihood of the given picks with the given input cubes. Turns out though, that I just need to check for each of the given cube in my total cubes if any of the sets in the game have a bigger amount and declare them physically impossible.

possibleGame : CubeSet -> Game -> Bool
possibleGame cubeset game = Dict.toList cubeset |>

    List.any
      ( \a ->
        (collectCubesOfColor game (Tuple.first a) |>
        List.maximum |>
        Maybe.withDefault 0) > (Tuple.second a)) |>
    not

collectCubesOfColor : Game -> Cube -> List Int
collectCubesOfColor game cube = List.map (Dict.get cube >> Maybe.withDefault 0) game.sets

This solution reads like plain english. A possible game is any game where none of the contained set's cube amount is higher than the input cubes of that color. To do that we iterate over each Cube amount pair of the input cubes, collect the same cubes of that color from each of the game's sets, get the maximum and check if it is higher than the target count.

Pasted image 20231203014528.png

Unfortunately, I am dead tired and need to sleep. So today there will be no part 2. But this will be an exception. See you on the next Chapter!

A few days later

Well today somehow I got everything done pretty quickly and day 6 was surprisingly easy, so here I am. I had a look at it and took a gander.

Pasted image 20231206205921.png

solve2 : Model -> String -> Int
solve2 model change =
  String.lines change |>
  List.map parseGame |>
  List.map minimalSet |>
  List.map List.product |>
  List.sum

  
minimalSet : Game -> List Int
minimalSet game =

    [Red, Blue, Green] |>
    List.map (collectCubesOfColor game) |>
    List.map List.maximum |>
    Maybe.values

Instead of collecting the games I map over all games with minimalSet, which collects one after another a list of all Red, Blue and Green cubes in a game gets the max, the result is then multiplied and summed up for each game.

Well, that was easy. Should have at least read it before dismissing it. But the completionist in me won't let you down :).

Full Solution

<< Day 1 |***| Day 3 >>