diff --git a/day01/default.nix b/day01/default.nix index c99d67c..d20f196 100644 --- a/day01/default.nix +++ b/day01/default.nix @@ -29,4 +29,4 @@ in pkgs.writeText "answers" '' Task2: ${answer2} -'' \ No newline at end of file +'' diff --git a/day02/default.nix b/day02/default.nix index 97ee6f3..f7fd532 100644 --- a/day02/default.nix +++ b/day02/default.nix @@ -10,11 +10,13 @@ let ]; inherentValue = x: { A = 1; B = 2; C = 3; }.${x}; + comparativeValue = { they, you }: if they == you then 3 else if (they == "A" && you == "B") || (they == "B" && you == "C") || (they == "C" && you == "A") then 6 else 0; + score = match: comparativeValue match + inherentValue match.you; equalizeValueType = x: { X = "A"; Y = "B"; Z = "C"; }.${x}; @@ -31,7 +33,9 @@ let Y = { A = 1; B = 2; C = 3; }; Z = { A = 2; B = 3; C = 1; }; }.${you}.${they}; + comparativeValue2 = x: { X = 0; Y = 3; Z = 6; }.${x}; + score2 = match: comparativeValue2 match.you + inherentValue2 match; answer2 = pipe guide [ @@ -46,4 +50,4 @@ in pkgs.writeText "answers" '' Task2: ${answer2} -'' \ No newline at end of file +'' diff --git a/day03/default.nix b/day03/default.nix index d9c56ac..df9b9f1 100644 --- a/day03/default.nix +++ b/day03/default.nix @@ -1,8 +1,10 @@ -{ pkgs, lib }: +{ pkgs, lib, AoCLib, ... }: with lib; let + inherit (AoCLib) transformRange chunksOf; + compartments = pipe (fileContents ./input.txt) [ (splitString "\n") ]; @@ -11,15 +13,15 @@ let c1 = substring 0 ((stringLength s) / 2) s; c2 = substring ((stringLength s) / 2) (stringLength s) s; }; + charsToSet = s: foldl (set: c: set // { ${c} = true; }) { } (stringToCharacters s); + getCommonChar = { c1, c2 }: findSingle (c: c2.${c} or false) "Error: no common chars" "Error: multiple chars" (attrNames c1); - mapRange = min: max: f: i: if min <= i && i < max then f i else i; - transformRange = min: max: offset: i: mapRange min max (x: x + offset) i; charValue = c: pipe c [ lib.strings.charToInt @@ -36,14 +38,12 @@ let toString ]; - chunksOf = n: l: if length l <= n - then [l] - else [(take n l)] ++ (chunksOf n (drop n l)); toA123 = l: { a1 = elemAt l 0; a2 = elemAt l 1; a3 = elemAt l 2; }; + getCommonChar2 = { a1, a2, a3 }: findSingle (c: a2.${c} or false && a3.${c} or false) diff --git a/day04/default.nix b/day04/default.nix index dd61647..fb55c5a 100644 --- a/day04/default.nix +++ b/day04/default.nix @@ -1,4 +1,4 @@ -{ pkgs, lib }: +{ pkgs, lib, ... }: with lib; @@ -43,4 +43,4 @@ in pkgs.writeText "answers" '' Task2: ${answer2} -'' \ No newline at end of file +'' diff --git a/day05/default.nix b/day05/default.nix index 1b4fb47..d87d497 100644 --- a/day05/default.nix +++ b/day05/default.nix @@ -1,4 +1,4 @@ -{ pkgs, lib }: +{ pkgs, lib, ... }: with lib; diff --git a/day06/default.nix b/day06/default.nix index 34b5729..2c3070e 100644 --- a/day06/default.nix +++ b/day06/default.nix @@ -1,8 +1,10 @@ -{ pkgs, lib }: +{ pkgs, lib, AoCLib, ... }: with lib; let + inherit (AoCLib) allUnique; + countWithNUntil = n: pred: list: let inner = list': count: if pred (take n list') @@ -10,13 +12,10 @@ let else inner (tail list') (count + 1); in inner list 0; - allItemsAreUnique = l: l == [] - || !(elem (head l) (tail l)) && allItemsAreUnique (tail l); - answerN = n: pipe ./input.txt [ fileContents stringToCharacters - (countWithNUntil n allItemsAreUnique) + (countWithNUntil n allUnique) (add n) toString ]; diff --git a/day07/default.nix b/day07/default.nix index c248290..769a254 100644 --- a/day07/default.nix +++ b/day07/default.nix @@ -1,4 +1,4 @@ -{ pkgs, lib }: +{ pkgs, lib, ... }: with lib; diff --git a/day08/default.nix b/day08/default.nix index 280713f..31e7f2e 100644 --- a/day08/default.nix +++ b/day08/default.nix @@ -1,8 +1,10 @@ -{ pkgs, lib }: +{ pkgs, lib, AoCLib, ... }: with lib; let + inherit (AoCLib) transpose countWhile multiply; + calculateTreeVisibilityForLine = line: let updateState = { currentMax ? (-1), trees ? [] }: tree: if tree > currentMax then { currentMax = tree; trees = trees ++ [true]; } @@ -11,9 +13,6 @@ let backwards = reverseList (foldr (flip updateState) { } line).trees; in zipListsWith or forwards backwards; - transpose = grid: - genList (n: map ((flip elemAt) n) grid) (length grid); - combineGridsWith = f: grid1: grid2: let height = length grid1; width = length (elemAt grid1 0); @@ -88,13 +87,13 @@ let visibleDistanceBackwards reverseList ]; - in zipListsWith (x: y: x * y) forwards backwards; + in zipListsWith multiply forwards backwards; answer2 = pipe trees [ (lines: { horizontal = lines; vertical = transpose lines; }) (mapAttrs (_: map visibleDistanceHorizontal)) ({horizontal, vertical}: { inherit horizontal; vertical = transpose vertical; }) - ({horizontal, vertical}: combineGridsWith (x: y: x * y) horizontal vertical) + ({horizontal, vertical}: combineGridsWith multiply horizontal vertical) (map (foldr max 0)) (foldr max 0) toString diff --git a/day09/default.nix b/day09/default.nix index 69bc143..3e38cf3 100644 --- a/day09/default.nix +++ b/day09/default.nix @@ -1,8 +1,10 @@ -{ pkgs, lib }: +{ pkgs, lib, AoCLib, ... }: with lib; let + inherit (AoCLib) scanl abs repeat cmp; + mapDirectionStepsToHorizontalVertical = { direction, steps }: { horizontal = if direction == "L" then steps else if direction == "R" then -steps else 0; @@ -10,11 +12,6 @@ let if direction == "U" then -steps else 0; }; - scanl = f: x1: list: let - x2 = head list; - x1' = f x1 x2; - in if list == [] then [] else [x1'] ++ (scanl f x1' (tail list)); - foldHeadPosition = { x ? 0, y ? 0 }: { horizontal, vertical }: { @@ -31,20 +28,14 @@ let (scanl foldHeadPosition {}) ]; - abs = x: if x < 0 then -x else x; - ropePieceLength = headPiece: tailPiece: let deltaX = abs (headPiece.x - tailPiece.x); deltaY = abs (headPiece.y - tailPiece.y); in max deltaX deltaY; moveRopePiece = headPiece: tailPiece: { - x = if headPiece.x > tailPiece.x then tailPiece.x + 1 else - if headPiece.x < tailPiece.x then tailPiece.x - 1 else - tailPiece.x; - y = if headPiece.y > tailPiece.y then tailPiece.y + 1 else - if headPiece.y < tailPiece.y then tailPiece.y - 1 else - tailPiece.y; + x = tailPiece.x + (cmp headPiece.x tailPiece.x); + y = tailPiece.y + (cmp headPiece.y tailPiece.y); }; moveRope = headToOverlap: rope: let @@ -69,8 +60,6 @@ let tailPositions = [(last newRope)] ++ nextIteration.tailPositions; }; - repeat = item: times: map (const item) (range 1 times); - f = n: { rope ? (repeat { x = 0; y = 0; } n), tailPositions ? [{ x = 0; y = 0; }] }: newHeadPosition: let newRope = moveRopeUntilHeadOverlapsAndReportLastPositions newHeadPosition rope; in { diff --git a/day10/default.nix b/day10/default.nix index a3cf278..08e0ba7 100644 --- a/day10/default.nix +++ b/day10/default.nix @@ -1,39 +1,9 @@ -{ pkgs, lib }: +{ pkgs, lib, AoCLib, ... }: with lib; let - # See https://github.com/NixOS/nixpkgs/pull/205457 - toInt = str: - let - inherit (builtins) match fromJSON; - # RegEx: Match any leading whitespace, possibly a '-', one or more digits, - # and finally match any trailing whitespace. - strippedInput = match "[[:space:]]*(-?[[:digit:]]+)[[:space:]]*" str; - - # RegEx: Match a leading '0' then one or more digits. - isLeadingZero = match "0[[:digit:]]+" (head strippedInput) == []; - - # Attempt to parse input - parsedInput = fromJSON (head strippedInput); - - generalError = "toInt: Could not convert ${escapeNixString str} to int."; - - octalAmbigError = "toInt: Ambiguity in interpretation of ${escapeNixString str}" - + " between octal and zero padded integer."; - - in - # Error on presence of non digit characters. - if strippedInput == null - then throw generalError - # Error on presence of leading zero/octal ambiguity. - else if isLeadingZero - then throw octalAmbigError - # Error if parse function fails. - else if !isInt parsedInput - then throw generalError - # Return result. - else parsedInput; + inherit (AoCLib) toInt repeat takeWithStride chunksOf; lineToInstruction = line: if line == "noop" @@ -48,17 +18,12 @@ let else { cycles = 2; X = nextX; nextX = nextX + instr.val; }; in if instructions == [] then [] else [nextSignalState] ++ (foldToSignalState nextSignalState (tail instructions)); - repeat = item: times: map (const item) (range 1 times); - expandSignalStates = signalStates: let s = head signalStates; in if signalStates == [] then [] else (repeat s.X s.cycles) ++ (expandSignalStates (tail signalStates)); - takeWithStride = n: l: - if l == [] then [] else [(head l)] ++ takeWithStride n (drop n l); - signalStates = pipe ./input.txt [ fileContents (splitString "\n") @@ -75,14 +40,10 @@ let toString ]; - splitAtInterval = n: list: - if list == [] then [] - else [(take n list)] ++ (splitAtInterval n (drop n list)); - f = i: v: if v <= i && i <= v + 2 then "#" else "."; answer2 = pipe signalStates [ - (splitAtInterval 40) + (chunksOf 40) (map (imap1 f)) (map (concatStringsSep "")) (concatStringsSep "\n") diff --git a/flake.nix b/flake.nix index c8c46d1..719a711 100644 --- a/flake.nix +++ b/flake.nix @@ -5,17 +5,18 @@ system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system}; in { + AoCLib = pkgs.callPackage ./lib.nix { }; packages.${system} = { - day01 = pkgs.callPackage ./day01 { }; - day02 = pkgs.callPackage ./day02 { }; - day03 = pkgs.callPackage ./day03 { }; - day04 = pkgs.callPackage ./day04 { }; - day05 = pkgs.callPackage ./day05 { }; - day06 = pkgs.callPackage ./day06 { }; - day07 = pkgs.callPackage ./day07 { }; - day08 = pkgs.callPackage ./day08 { }; - day09 = pkgs.callPackage ./day09 { }; - day10 = pkgs.callPackage ./day10 { }; + day01 = pkgs.callPackage ./day01 { inherit (self) AoCLib; }; + day02 = pkgs.callPackage ./day02 { inherit (self) AoCLib; }; + day03 = pkgs.callPackage ./day03 { inherit (self) AoCLib; }; + day04 = pkgs.callPackage ./day04 { inherit (self) AoCLib; }; + day05 = pkgs.callPackage ./day05 { inherit (self) AoCLib; }; + day06 = pkgs.callPackage ./day06 { inherit (self) AoCLib; }; + day07 = pkgs.callPackage ./day07 { inherit (self) AoCLib; }; + day08 = pkgs.callPackage ./day08 { inherit (self) AoCLib; }; + day09 = pkgs.callPackage ./day09 { inherit (self) AoCLib; }; + day10 = pkgs.callPackage ./day10 { inherit (self) AoCLib; }; }; }; } diff --git a/lib.nix b/lib.nix new file mode 100644 index 0000000..f4c0b4f --- /dev/null +++ b/lib.nix @@ -0,0 +1,117 @@ +{ pkgs, lib }: with lib; rec { + # Transpose a square grid + # + # [[a]] -> [[a]] + transpose = grid: + genList (n: map ((flip elemAt) n) grid) (length grid); + + # Checks if there are any duplicate items in the list, + # in which case it returns false. + # + # [a] -> Bool + allUnique = list: list == [] + || !(elem (head list) (tail list)) && allUnique (tail list); + + # Takes items until either a predicate fails, or the list is empty. + # + # (a -> Bool) -> [a] -> Int + takeWhile = pred: xs: + if xs == [] || !(stopPred (head xs)) + then [] + else [(head xs)] + (takeWhile pred (tail xs)); + + # Counts items until either a predicate fails, or the list is empty + # + # (a -> Bool) -> [a] -> Int + countWhile = pred: xs: length (takeWhile head xs); + + # Like foldl, but keeps all intermediate values + # + # (b -> a -> b) -> b -> [a] -> [b] + scanl = f: x1: list: let + x2 = head list; + x1' = f x1 x2; + in if list == [] then [] else [x1'] ++ (scanl f x1' (tail list)); + + # Like scanl, but uses the first element as its start element. + # + # (a -> a -> a) -> [a] -> [a] + scanl1 = f: list: + if list == [] then [] else scanl f (head list) (tail list); + + # See https://github.com/NixOS/nixpkgs/pull/205457 + toInt = str: + let + inherit (builtins) match fromJSON; + # RegEx: Match any leading whitespace, possibly a '-', one or more digits, + # and finally match any trailing whitespace. + strippedInput = match "[[:space:]]*(-?[[:digit:]]+)[[:space:]]*" str; + + # RegEx: Match a leading '0' then one or more digits. + isLeadingZero = match "0[[:digit:]]+" (head strippedInput) == []; + + # Attempt to parse input + parsedInput = fromJSON (head strippedInput); + + generalError = "toInt: Could not convert ${escapeNixString str} to int."; + + octalAmbigError = "toInt: Ambiguity in interpretation of ${escapeNixString str}" + + " between octal and zero padded integer."; + + in + # Error on presence of non digit characters. + if strippedInput == null + then throw generalError + # Error on presence of leading zero/octal ambiguity. + else if isLeadingZero + then throw octalAmbigError + # Error if parse function fails. + else if !isInt parsedInput + then throw generalError + # Return result. + else parsedInput; + + # Trivial function to wrap around the multiplication operation. + # + # Number -> Number -> Number + multiply = x: y: x * y; + + # Trivial function to take the absolute value of a number + # + # Number -> Number + abs = x: if x < 0 then -x else x; + + # Generate a list by repeating an element n times. + # + # a -> Int -> [a] + repeat = item: times: map (const item) (range 1 times); + + # Compare two items, return either 1, 0, or -1 depending on whether + # one is bigger than the other. + # + # Ord a => a -> a -> Int + cmp = x: y: if x > y then 1 else if x < y then -1 else 0; + + # Take 1 item, and skip the n-1 next items continuosly until the list is empty. + # + # Int -> [a] -> [a] + takeWithStride = n: l: + if l == [] then [] else [(head l)] ++ takeWithStride n (drop n l); + + # Split a list at every n items. + # + # Int -> [a] -> [[a]] + chunksOf = n: list: if list == [] + then [] + else [(take n list)] ++ (chunksOf n (drop n list)); + + # Map something orderable through a function f if it is between min and max. + # + # Ord a => a -> a -> (a -> b) -> a -> Either a b + mapRange = min: max: f: o: if min <= o && o < max then f o else o; + + # Shift a number n by an offset if it is between min and max. + # + # Number -> Number -> Number -> Number -> Number + transformRange = min: max: offset: n: mapRange min max (x: x + offset) n; +}