We're going to solve an advent of code puzzle in Roc lang. These are the functions you can use: ```roc Bool.false : Bool Bool.true : Bool Decode.fromBytesPartial : List U8, fmt -> DecodeResult val where val implements Decoding, fmt implements DecoderFormatting Dict.empty : {} -> Dict * * Dict.fromList : List (k, v) -> Dict k v Dict.get : Dict k v, k -> Result v [KeyNotFound] Dict.insert : Dict k v, k, v -> Dict k v Dict.insertAll : Dict k v, Dict k v -> Dict k v Dict.joinMap : Dict a b, (a, b -> Dict x y) -> Dict x y Dict.keys : Dict k v -> List k Dict.map : Dict k a, (k, a -> b) -> Dict k b Dict.remove : Dict k v, k -> Dict k v Dict.single : k, v -> Dict k v Dict.toList : Dict k v -> List (k, v) Dict.update : Dict k v, k, (Result v [Missing] -> Result v [Missing]) -> Dict k v Dict.walk : Dict k v, state, (state, k, v -> state) -> state Encode.toBytes : val, fmt -> List U8 where val implements Encoding, fmt implements EncoderFormatting List.all : List a, (a -> Bool) -> Bool List.any : List a, (a -> Bool) -> Bool List.append : List a, a -> List a List.chunksOf : List a, U64 -> List (List a) List.concat : List a, List a -> List a List.contains : List a, a -> Bool where a implements Eq List.dropAt : List elem, U64 -> List elem List.dropFirst : List elem, U64 -> List elem List.dropIf : List a, (a -> Bool) -> List a List.dropLast : List elem, U64 -> List elem List.findFirst : List elem, (elem -> Bool) -> Result elem [NotFound] List.findFirstIndex : List elem, (elem -> Bool) -> Result U64 [NotFound] List.findLastIndex : List elem, (elem -> Bool) -> Result U64 [NotFound] List.first : List a -> Result a [ListWasEmpty] List.get : List a, U64 -> Result a [OutOfBounds] List.intersperse : List elem, elem -> List elem List.isEmpty : List * -> Bool List.join : List (List a) -> List a List.joinMap : List a, (a -> List b) -> List b List.keepIf : List a, (a -> Bool) -> List a List.keepOks : List before, (before -> Result after *) -> List after List.last : List a -> Result a [ListWasEmpty] List.len : List * -> U64 List.map : List a, (a -> b) -> List b List.map2 : List a, List b, (a, b -> c) -> List c List.mapTry : List elem, (elem -> Result ok err) -> Result (List ok) err List.mapWithIndex : List a, (a, U64 -> b) -> List b List.max : List (Num a) -> Result (Num a) [ListWasEmpty] List.min : List (Num a) -> Result (Num a) [ListWasEmpty] List.range : { end : [At (Num a), Before (Num a), Length U64], start : [After (Num a), At (Num a)], step ? Num a }* -> List (Num a) List.repeat : a, U64 -> List a List.replace : List a, U64, a -> { list : List a, value : a } List.reverse : List a -> List a List.sortAsc : List (Num a) -> List (Num a) List.sortDesc : List (Num a) -> List (Num a) List.sortWith : List a, (a, a -> [LT, EQ, GT]) -> List a List.splitAt : List elem, U64 -> { before : List elem, others : List elem } List.splitOn : List a, a -> List (List a) where a implements Eq List.sublist : List elem, { start : U64, len : U64 } -> List elem List.sum : List (Num a) -> Num a List.swap : List a, U64, U64 -> List a List.takeFirst : List elem, U64 -> List elem List.takeLast : List elem, U64 -> List elem List.update : List a, U64, (a -> a) -> List a List.walk : List elem, state, (state, elem -> state) -> state List.walkBackwards : List elem, state, (state, elem -> state) -> state List.walkTry : List elem, state, (state, elem -> Result state err) -> Result state err List.walkUntil : List elem, state, (state, elem -> [Continue state, Break state]) -> state List.walkWithIndex : List elem, state, (state, elem, U64 -> state) -> state List.walkWithIndexUntil : List elem, state, (state, elem, U64 -> [Continue state, Break state]) -> state List.withCapacity : U64 -> List * Num.abs : Num a -> Num a Num.add : Num a, Num a -> Num a Num.addChecked : Num a, Num a -> Result (Num a) [Overflow] Num.bitwiseAnd : Int a, Int a -> Int a Num.bitwiseOr : Int a, Int a -> Int a Num.ceiling : Frac * -> Int * Num.compare : Num a, Num a -> [LT, EQ, GT] Num.cos : Frac a -> Frac a Num.div : Frac a, Frac a -> Frac a Num.divTrunc : Int a, Int a -> Int a Num.divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero] Num.e : Frac * Num.intCast : Int a -> Int b Num.isApproxEq : Frac a, Frac a, { rtol ? Frac a, atol ? Frac a } -> Bool Num.isEven : Int a -> Bool Num.isMultipleOf : Int a, Int a -> Bool Num.max : Num a, Num a -> Num a Num.maxU64 : U64 Num.min : Num a, Num a -> Num a Num.mul : Num a, Num a -> Num a Num.pow : Frac a, Frac a -> Frac a Num.powInt : Int a, Int a -> Int a Num.rem : Int a, Int a -> Int a Num.round : Frac * -> Int * Num.shiftLeftBy : Int a, U8 -> Int a Num.shiftRightZfBy : Int a, U8 -> Int a Num.sin : Frac a -> Frac a Num.sqrt : Frac a -> Frac a Num.subChecked : Num a, Num a -> Result (Num a) [Overflow] Num.subSaturated : Num a, Num a -> Num a Num.toF64 : Num * -> F64 Num.toFrac : Num * -> Frac * Num.toI64 : Int * -> I64 Num.toI8 : Int * -> I8 Num.toStr : Num * -> Str Num.toU16 : Int * -> U16 Num.toU32 : Int * -> U32 Num.toU64 : Int * -> U64 Num.toU64Checked : Int * -> Result U64 [OutOfBounds] Num.toU8 : Int * -> U8 Result.isOk : Result ok err -> Bool Result.mapErr : Result ok a, (a -> b) -> Result ok b Result.onErr : Result a err, (err -> Result a otherErr) -> Result a otherErr Result.try : Result a err, (a -> Result b err) -> Result b err Result.withDefault : Result ok err, ok -> ok Result.map : Result a err, (a -> b) -> Result b err Result.map2 : Result a err, Result b err, (a, b -> c) -> Result c err Set.contains : Set k, k -> Bool Set.empty : {} -> Set * Set.fromList : List k -> Set k Set.insert : Set k, k -> Set k Set.intersection : Set k, Set k -> Set k Set.isEmpty : Set * -> Bool Set.keepIf : Set k, (k -> Bool) -> Set k Set.len : Set * -> U64 Set.map : Set a, (a -> b) -> Set b Set.remove : Set k, k -> Set k Set.single : k -> Set k Set.toList : Set k -> List k Set.union : Set k, Set k -> Set k Set.walkUntil : Set k, state, (state, k -> [Continue state, Break state]) -> state Str.contains : Str, Str -> Bool Str.endsWith : Str, Str -> Bool Str.fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem U64] Str.isEmpty : Str -> Bool Str.joinWith : List Str, Str -> Str Str.repeat : Str, U64 -> Str Str.replaceEach : Str, Str, Str -> Str Str.replaceFirst : Str, Str, Str -> Str Str.splitFirst : Str, Str -> Result { before : Str, after : Str } [NotFound] Str.splitOn : Str, Str -> List Str Str.startsWith : Str, Str -> Bool Str.toI16 : Str -> Result I16 [InvalidNumStr] Str.toI64 : Str -> Result I64 [InvalidNumStr] Str.toU32 : Str -> Result U32 [InvalidNumStr] Str.toU64 : Str -> Result U64 [InvalidNumStr] Str.toUtf8 : Str -> List U8 Str.trim : Str -> Str Str.trimEnd : Str -> Str Task.map : Task a c, (a -> b) -> Task b c ``` Some examples of previous advent of code puzzles from 2023: # 1 ## Puzzle --- Day 1: Trebuchet?! --- Something is wrong with global snow production, and you've been selected to take a look. The Elves have even given you a map; on it, they've used stars to mark the top fifty locations that are likely to be having problems. You've been doing this long enough to know that to restore snow operations, you need to check all fifty stars by December 25th. Collect stars by solving puzzles. Two puzzles will be made available on each day in the Advent calendar; the second puzzle is unlocked when you complete the first. Each puzzle grants one star. Good luck! You try to ask why they can't just use a weather machine ("not powerful enough") and where they're even sending you ("the sky") and why your map looks mostly blank ("you sure ask a lot of questions") and hang on did you just say the sky ("of course, where do you think snow comes from") when you realize that the Elves are already loading you into a trebuchet ("please hold still, we need to strap you in"). As they're making the final adjustments, they discover that their calibration document (your puzzle input) has been amended by a very young Elf who was apparently just excited to show off her art skills. Consequently, the Elves are having trouble reading the values on the document. The newly-improved calibration document consists of lines of text; each line originally contained a specific calibration value that the Elves now need to recover. On each line, the calibration value can be found by combining the first digit and the last digit (in that order) to form a single two-digit number. For example: 1abc2 pqr3stu8vwx a1b2c3d4e5f treb7uchet In this example, the calibration values of these four lines are 12, 38, 15, and 77. Adding these together produces 142. Consider your entire calibration document. What is the sum of all of the calibration values? ## Solution ```roc app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", aoc: "https://github.com/lukewilliamboswell/aoc-template/releases/download/0.2.0/tlS1ZkwSKSB87_3poSOXcwHyySe0WxWOWQbPmp7rxBw.tar.br", } import pf.Stdin import pf.Stdout import pf.Utc import aoc.AoC { stdin: Stdin.readToEnd, stdout: Stdout.write, time: \{} -> Utc.now {} |> Task.map Utc.toMillisSinceEpoch, } main = AoC.solve { year: 2023, day: 1, title: "Trebuchet?!", part1, part2, } exampleInputPart1 = """ 1abc2 pqr3stu8vwx a1b2c3d4e5f treb7uchet """ exampleInputPart2 = """ two1nine eightwothree abcone2threexyz xtwone3four 4nineeightseven2 zoneight234 7pqrstsixteen """ part1 : Str -> Result Str [NotImplemented, Error Str] part1 = \input -> vals = input |> Str.splitOn "\n" |> List.map Str.toUtf8 |> List.map \bytes -> bytes |> List.keepIf isDigit |> List.map toCalibration Ok "The sum of all of the calibration values $(vals |> List.sum |> Num.toStr)" expect part1 exampleInputPart1 == Ok "The sum of all of the calibration values 142" part2 : Str -> Result Str [NotImplemented, Error Str] part2 = \input -> sum = input |> Str.splitOn "\n" |> List.map Str.toUtf8 |> List.map \bytes -> takeDigits { digits: [], rest: bytes } |> List.map toCalibration |> List.sum Ok "The sum of all of the calibration values $(Num.toStr sum)" expect part2 exampleInputPart2 == Ok "The sum of all of the calibration values 281" isDigit : U8 -> Bool isDigit = \b -> b >= '0' && b <= '9' toCalibration : List U8 -> U32 toCalibration = \digits -> when (List.first digits, List.last digits) is (Ok first, Ok last) -> when [first, last] |> Str.fromUtf8 |> Result.try Str.toU32 is Ok n -> n Err _ -> crash "couldnt parse number" _ -> crash "expected at least one number" takeDigits : { digits : List U8, rest : List U8 } -> List U8 takeDigits = \{ digits, rest } -> when rest is [] -> digits [a, ..] if isDigit a -> takeDigits { digits: List.append digits a, rest: List.dropFirst rest 1 } ['o', 'n', 'e', ..] -> takeDigits { digits: List.append digits '1', rest: List.dropFirst rest 1 } ['t', 'w', 'o', ..] -> takeDigits { digits: List.append digits '2', rest: List.dropFirst rest 1 } ['t', 'h', 'r', 'e', 'e', ..] -> takeDigits { digits: List.append digits '3', rest: List.dropFirst rest 1 } ['f', 'o', 'u', 'r', ..] -> takeDigits { digits: List.append digits '4', rest: List.dropFirst rest 1 } ['f', 'i', 'v', 'e', ..] -> takeDigits { digits: List.append digits '5', rest: List.dropFirst rest 1 } ['s', 'i', 'x', ..] -> takeDigits { digits: List.append digits '6', rest: List.dropFirst rest 1 } ['s', 'e', 'v', 'e', 'n', ..] -> takeDigits { digits: List.append digits '7', rest: List.dropFirst rest 1 } ['e', 'i', 'g', 'h', 't', ..] -> takeDigits { digits: List.append digits '8', rest: List.dropFirst rest 1 } ['n', 'i', 'n', 'e', ..] -> takeDigits { digits: List.append digits '9', rest: List.dropFirst rest 1 } _ -> takeDigits { digits, rest: List.dropFirst rest 1 } ``` # 2 ## Puzzle --- Day 3: Gear Ratios --- You and the Elf eventually reach a gondola lift station; he says the gondola lift will take you up to the water source, but this is as far as he can bring you. You go inside. It doesn't take long to find the gondolas, but there seems to be a problem: they're not moving. "Aaah!" You turn around to see a slightly-greasy Elf with a wrench and a look of surprise. "Sorry, I wasn't expecting anyone! The gondola lift isn't working right now; it'll still be a while before I can fix it." You offer to help. The engineer explains that an engine part seems to be missing from the engine, but nobody can figure out which one. If you can add up all the part numbers in the engine schematic, it should be easy to work out which part is missing. The engine schematic (your puzzle input) consists of a visual representation of the engine. There are lots of numbers and symbols you don't really understand, but apparently any number adjacent to a symbol, even diagonally, is a "part number" and should be included in your sum. (Periods (.) do not count as a symbol.) Here is an example engine schematic: 467..114.. ...*...... ..35..633. ......#... 617*...... .....+.58. ..592..... ......755. ...$.*.... .664.598.. In this schematic, two numbers are not part numbers because they are not adjacent to a symbol: 114 (top right) and 58 (middle right). Every other number is adjacent to a symbol and so is a part number; their sum is 4361. Of course, the actual engine schematic is much larger. What is the sum of all of the part numbers in the engine schematic? ## Solution ```roc app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", aoc: "https://github.com/lukewilliamboswell/aoc-template/releases/download/0.2.0/tlS1ZkwSKSB87_3poSOXcwHyySe0WxWOWQbPmp7rxBw.tar.br", } import pf.Stdin import pf.Stdout import pf.Utc import aoc.AoC { stdin: Stdin.readToEnd, stdout: Stdout.write, time: \{} -> Utc.now {} |> Task.map Utc.toMillisSinceEpoch, } main = AoC.solve { year: 2023, day: 3, title: "Gear Ratios", part1, part2, } exampleInput = """ 467..114.. ...*...... ..35..633. ......#... 617*...... .....+.58. ..592..... ......755. ...$.*.... .664.598.. """ Location : { row : U64, col : U64 } Token : [Digit U8, Dot, Symbol [Asterisk, Slash, Equal, Ampersand, At, Plus, Hash, Minus, Dollar, Percent], Number U64] LocationToken : { loc : Location, token : Token} Schematic : Dict Location Token part1 : Str -> Result Str [NotImplemented, Error Str] part1 = \input -> inputSchematic : Schematic inputSchematic = input |> parseLocationTokens |> filterDots |> toSchematic |> replaceDigitsWithNumbers sum = inputSchematic |> symbolLocations |> List.map (adjacentLocations inputSchematic) |> List.map filterUniqueNumbers |> List.map sumPartNumbers # assume each number can only see 1 symbol, otherwise we will double count |> List.sum Ok "The sum of all of the part numbers in the engine schematic is $(Num.toStr sum)" expect part1 exampleInput == Ok "The sum of all of the part numbers in the engine schematic is 4361" part2 : Str -> Result Str [NotImplemented, Error Str] part2 = \input -> inputSchematic : Schematic inputSchematic = input |> parseLocationTokens |> filterDots |> toSchematic |> replaceDigitsWithNumbers sum = inputSchematic |> gearLocations |> List.map (adjacentLocations inputSchematic) |> List.map filterUniqueNumbers |> List.keepIf \lts -> hasExactlyTwoParts lts [] |> List.map calculateGearRatio |> List.sum Ok "The the sum of all of the gear ratios in the engine schematic is $(Num.toStr sum)" expect part2 exampleInput == Ok "The the sum of all of the gear ratios in the engine schematic is 467835" parseLocationTokens : Str -> List (List LocationToken) parseLocationTokens = \input -> input |> Str.splitOn "\n" |> List.mapWithIndex \rowStr, row -> rowStr |> Str.toUtf8 |> List.mapWithIndex \byte, col -> {loc : {row, col}, token: tokenFromByte byte } tokenFromByte : U8 -> Token tokenFromByte = \b -> when b is a if a >= '0' && a <= '9' -> Digit (a - '0') '*' -> Symbol Asterisk '/' -> Symbol Slash '=' -> Symbol Equal '&' -> Symbol Ampersand '@' -> Symbol At '#' -> Symbol Hash '+' -> Symbol Plus '-' -> Symbol Minus '$' -> Symbol Dollar '%' -> Symbol Percent '.' -> Dot _ -> str = [b] |> Str.fromUtf8 |> Result.withDefault "" crash "token '$(str)' not recognised " expect tokenFromByte '.' == Dot expect tokenFromByte '2' == Digit 2u8 expect tokenFromByte '$' == Symbol Dollar isDot : {token : Token}a -> Bool isDot = \{token} -> token != Dot expect !(isDot {loc: {row: 0, col: 0}, token: Dot}) expect isDot {loc: {row: 0, col: 0}, token: Digit 0} filterDots : List (List LocationToken) -> List LocationToken filterDots = \lts -> lts |> List.join |> List.keepIf isDot toSchematic : List LocationToken -> Schematic toSchematic = \lts -> List.walk lts (Dict.empty {}) \dict, lt -> Dict.insert dict lt.loc lt.token symbolLocations : Schematic -> List Location symbolLocations = \schematic -> Dict.walk schematic [] \acc, loc, token -> when token is Symbol _ -> List.append acc loc _ -> acc # increasing row is down # increasing col is right move : Location -> ([UpLeft, UpRight, DownLeft, DownRight, Left, Up, Right, Down] -> Result Location [Invalid]) move = \{row, col} -> \direction -> maybeRow = when direction is UpLeft -> Num.subChecked row 1 UpRight -> Num.subChecked row 1 DownLeft -> Num.addChecked row 1 DownRight -> Num.addChecked row 1 Left -> Ok row Up -> Num.subChecked row 1 Right -> Ok row Down -> Num.addChecked row 1 maybeCol = when direction is UpLeft -> Num.subChecked col 1 UpRight -> Num.addChecked col 1 DownLeft -> Num.subChecked col 1 DownRight -> Num.addChecked col 1 Left -> Num.subChecked col 1 Up -> Ok col Right -> Num.addChecked col 1 Down -> Ok col when (maybeRow, maybeCol) is (Ok r, Ok c) -> Ok {row: r, col: c} _ -> Err Invalid getToken : Schematic -> (Location -> Result LocationToken [NothingAtLocation]) getToken = \schematic -> \loc -> when Dict.get schematic loc is Ok token -> Ok {loc, token} Err KeyNotFound -> Err NothingAtLocation adjacentLocations : Schematic -> (Location -> List LocationToken) adjacentLocations = \schematic -> \curr -> [UpLeft,UpRight,DownLeft,DownRight,Left,Up,Right,Down] |> List.keepOks (move curr) |> List.keepOks (getToken schematic) # take a schematic and convert any digits into the number at that location replaceDigitsWithNumbers : Schematic -> Schematic replaceDigitsWithNumbers = \schematic -> Dict.map schematic \loc, token -> when token is Digit _ -> Number (getNumberAtLocation schematic loc WalkingLeft) _ -> token # start with a digit, walk left to first digit, then walk right building up the number getNumberAtLocation : Schematic, Location, [WalkingLeft, BuildingNumber U64] -> U64 getNumberAtLocation = \schematic, loc, state -> maybeRight = (move loc) Right |> Result.try \right -> when Dict.get schematic right is Ok (Digit u8) -> Ok (right, u8) _ -> Err Invalid maybeLeft = (move loc) Left |> Result.try \left -> when Dict.get schematic left is Ok (Digit u8) -> Ok (left, u8) _ -> Err Invalid maybeCurrent = when Dict.get schematic loc is Ok (Digit u8) -> Ok (loc, u8) _ -> Err Invalid when (state, maybeLeft, maybeRight, maybeCurrent ) is (WalkingLeft, Ok (left, _), _, Ok _ ) -> getNumberAtLocation schematic left WalkingLeft (WalkingLeft, _, _, Err _ ) -> crash "starting location isn't a digit or isn't in schematic" (WalkingLeft, _, Ok (right, _), Ok (_, u8) ) -> getNumberAtLocation schematic right (BuildingNumber (Num.toU64 u8)) (WalkingLeft, _, _, Ok (_, u8) ) -> Num.toU64 u8 # single number (WalkingLeft, _, _, _ ) -> crash "unable to move right" (BuildingNumber u64, _, Ok (right, _), Ok (_, u8) ) -> getNumberAtLocation schematic right (BuildingNumber ((u64 * 10) + (Num.toU64 u8))) (BuildingNumber u64, _, _, Ok (_, u8) ) -> (u64 * 10) + (Num.toU64 u8) # base case (BuildingNumber _, _, _, _ ) -> crash "moved to a location that isnt a number" # take a list of location/tolen pairs and keep only unique numbers filterUniqueNumbers : List LocationToken -> List LocationToken filterUniqueNumbers = \lts -> filterUniqueNumbersHelp lts [] [] filterUniqueNumbersHelp : List LocationToken, List U64, List LocationToken -> List LocationToken filterUniqueNumbersHelp = \lts, seen, keep -> next = List.dropFirst lts 1 when lts is [] -> keep # base case, keep these locations [first, ..] -> when first.token is Number u64 if !(List.contains seen u64) -> # we haven't seen this number before filterUniqueNumbersHelp next (List.append seen u64) (List.append keep first) _ -> filterUniqueNumbersHelp next seen keep # ignore anything that is not a number sumPartNumbers : List LocationToken -> U64 sumPartNumbers = \lts -> next = List.dropFirst lts 1 when lts is [] -> 0 # base case [first, ..] -> when first.token is Number u64 -> u64 + (sumPartNumbers next) # next _ -> crash "expected only numbers" gearLocations : Schematic -> List Location gearLocations = \schematic -> Dict.walk schematic [] \acc, loc, token -> when token is Symbol Asterisk -> List.append acc loc _ -> acc hasExactlyTwoParts : List LocationToken, List U64 -> Bool hasExactlyTwoParts = \lts, seen -> next = List.dropFirst lts 1 when lts is [] -> List.len seen == 2 [first, .. ] -> when first.token is Number u64 -> hasExactlyTwoParts next (List.append seen u64) _ -> crash "expect only numbers" calculateGearRatio : List LocationToken -> U64 calculateGearRatio = \lts -> expect List.len lts <= 2 next = List.dropFirst lts 1 when lts is [] -> 1 # base case [first, ..] -> when first.token is Number u64 -> u64 * (calculateGearRatio next) # next _ -> crash "expected only numbers" ``` We're now going to solve a puzzle for advent of code 2024. ## Puzzle --- Day 9: Disk Fragmenter --- Another push of the button leaves you in the familiar hallways of some friendly amphipods! Good thing you each somehow got your own personal mini submarine. The Historians jet away in search of the Chief, mostly by driving directly into walls. While The Historians quickly figure out how to pilot these things, you notice an amphipod in the corner struggling with his computer. He's trying to make more contiguous free space by compacting all of the files, but his program isn't working; you offer to help. He shows you the disk map (your puzzle input) he's already generated. For example: 2333133121414131402 The disk map uses a dense format to represent the layout of files and free space on the disk. The digits alternate between indicating the length of a file and the length of free space. So, a disk map like 12345 would represent a one-block file, two blocks of free space, a three-block file, four blocks of free space, and then a five-block file. A disk map like 90909 would represent three nine-block files in a row (with no free space between them). Each file on disk also has an ID number based on the order of the files as they appear before they are rearranged, starting with ID 0. So, the disk map 12345 has three files: a one-block file with ID 0, a three-block file with ID 1, and a five-block file with ID 2. Using one character for each block where digits are the file ID and . is free space, the disk map 12345 represents these individual blocks: 0..111....22222 The first example above, 2333133121414131402, represents these individual blocks: 00...111...2...333.44.5555.6666.777.888899 The amphipod would like to move file blocks one at a time from the end of the disk to the leftmost free space block (until there are no gaps remaining between file blocks). For the disk map 12345, the process looks like this: 0..111....22222 02.111....2222. 022111....222.. 0221112...22... 02211122..2.... 022111222...... The first example requires a few more steps: 00...111...2...333.44.5555.6666.777.888899 009..111...2...333.44.5555.6666.777.88889. 0099.111...2...333.44.5555.6666.777.8888.. 00998111...2...333.44.5555.6666.777.888... 009981118..2...333.44.5555.6666.777.88.... 0099811188.2...333.44.5555.6666.777.8..... 009981118882...333.44.5555.6666.777....... 0099811188827..333.44.5555.6666.77........ 00998111888277.333.44.5555.6666.7......... 009981118882777333.44.5555.6666........... 009981118882777333644.5555.666............ 00998111888277733364465555.66............. 0099811188827773336446555566.............. The final step of this file-compacting process is to update the filesystem checksum. To calculate the checksum, add up the result of multiplying each of these blocks' position with the file ID number it contains. The leftmost block is in position 0. If a block contains free space, skip it instead. Continuing the first example, the first few blocks' position multiplied by its file ID number are 0 * 0 = 0, 1 * 0 = 0, 2 * 9 = 18, 3 * 9 = 27, 4 * 8 = 32, and so on. In this example, the checksum is the sum of these, 1928. Compact the amphipod's hard drive using the process he requested. What is the resulting filesystem checksum? --- Part Two --- Upon completion, two things immediately become clear. First, the disk definitely has a lot more contiguous free space, just like the amphipod hoped. Second, the computer is running much more slowly! Maybe introducing all of that file system fragmentation was a bad idea? The eager amphipod already has a new plan: rather than move individual blocks, he'd like to try compacting the files on his disk by moving whole files instead. This time, attempt to move whole files to the leftmost span of free space blocks that could fit the file. Attempt to move each file exactly once in order of decreasing file ID number starting with the file with the highest file ID number. If there is no span of free space to the left of a file that is large enough to fit the file, the file does not move. The first example from above now proceeds differently: 00...111...2...333.44.5555.6666.777.888899 0099.111...2...333.44.5555.6666.777.8888.. 0099.1117772...333.44.5555.6666.....8888.. 0099.111777244.333....5555.6666.....8888.. 00992111777.44.333....5555.6666.....8888.. The process of updating the filesystem checksum is the same; now, this example's checksum would be 2858. Start over, now compacting the amphipod's hard drive using this new method instead. What is the resulting filesystem checksum ## Solution ```roc app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", aoc: "https://github.com/lukewilliamboswell/aoc-template/releases/download/0.2.0/tlS1ZkwSKSB87_3poSOXcwHyySe0WxWOWQbPmp7rxBw.tar.br", } import pf.Stdin import pf.Stdout import pf.Utc import aoc.AoC { stdin: Stdin.readToEnd, stdout: Stdout.write, time: \{} -> Utc.now {} |> Task.map Utc.toMillisSinceEpoch, } main = AoC.solve { year: 2024, day: 9, title: "Disk Fragmenter", part1, part2, } exampleInput = "2333133121414131402" Block : [File { id : U64, size : U64 }, Space U64] part1 : Str -> Result Str [NotImplemented, Error Str] part1 = \input -> blocks = parseInput input expandedBlocks = expandBlocks blocks compactedBlocks = compactDisk expandedBlocks checksum = calculateChecksum compactedBlocks Ok "The resulting filesystem checksum is $(Num.toStr checksum)" expect result = part1 exampleInput result == Ok "The resulting filesystem checksum is 1928" part2 : Str -> Result Str [NotImplemented, Error Str] part2 = \_ -> Err NotImplemented parseInput : Str -> List Block parseInput = \input -> input |> Str.toUtf8 |> List.map \byte -> byte - '0' |> List.walkWithIndex ([], 0) \(blocks, fileId), size, index -> if Num.isEven index then (List.append blocks (File { id: fileId, size: Num.toU64 size }), fileId + 1) else (List.append blocks (Space (Num.toU64 size)), fileId) |> \(blocks, _) -> blocks expandBlocks : List Block -> List Block expandBlocks = \blocks -> blocks |> List.walk [] \expanded, block -> when block is File { id, size } -> List.concat expanded (List.repeat (File { id, size: 1 }) size) Space size -> List.concat expanded (List.repeat (Space 1) size) compactDisk : List Block -> List Block compactDisk = \blocks -> # Find first space and last file firstSpaceIndex = blocks |> List.findFirstIndex \block -> when block is Space _ -> Bool.true _ -> Bool.false lastFileIndex = blocks |> List.findLastIndex \block -> when block is File _ -> Bool.true _ -> Bool.false when (firstSpaceIndex, lastFileIndex) is (Ok spaceIdx, Ok fileIdx) if spaceIdx < fileIdx -> # Move the file to the space file = blocks |> List.get fileIdx |> Result.withDefault (Space 0) blocksWithoutFile = blocks |> List.dropAt fileIdx blocksWithFileInSpace = blocksWithoutFile |> List.replace spaceIdx file |> .list compactDisk blocksWithFileInSpace _ -> blocks # No more compaction needed calculateChecksum : List Block -> U64 calculateChecksum = \blocks -> blocks |> List.mapWithIndex \block, index -> when block is File { id, size: _ } -> index * id Space _ -> 0 |> List.sum ``` Implement part 2 of the puzzle. Extra instructions: - If the user provides you with an error, start your reply with an analysis of what could be going wrong paired with potential solutions. There is no need to provide an explanation after the code block. - Always reply with the full code, no partial snippets with changes. - Do not use the Parser package. - String interpolation works like this: `"Two plus three is: $(Num.toStr (2 + 3))"`. - Roc does not use `head :: tail`, use `[head, .. as tail]` instead. Make sure you do not forget the `as`! - Roc does not have currying. - You can not pattern match directly on booleans. Avoid using Bool.true and Bool.false in general, it is better to use descriptive tags, for example `HasNoExtraLine` and `HasExtraLine`. - Roc does not allow shadowing. - Make sure to avoid unused variables, types, or arguments. - Roc can hit an exhaustiveness bug in pattern matching where it says "Other possibilities include:" when in reality all possibilites are covered, if that bug happens include a branch like this: ``` _ -> crash "Roc bug, the previous branches actually are exhaustive." ``` - Use intermediary variables in your expect tests, all variables will be printed on failure, that will make debugging easier. Example: ``` expect part1Result = part1 exampleInput part1Result == Ok "The sum is 161" ``` - You can use `dbg` for debugging, `dbg` does not work with intermediary variables like `expect`. Examples: ``` # standalone version dbg count # expression version if sum == 1 then dbg (someFunction foo) else someFunction bar ``` - Make sure to properly format variables of multiline strings. Example: ``` exampleInputPart1 = """ 1abc2 pqr3stu8vwx a1b2c3d4e5f treb7uchet """ ```