diff --git a/2024/16/solution.nix b/2024/16/solution.nix index a5adb3a..452760f 100644 --- a/2024/16/solution.nix +++ b/2024/16/solution.nix @@ -3,7 +3,7 @@ inherit (lib) splitString mod range; inherit (lib.lists) findFirstIndex imap0; inherit (lib.strings) stringToCharacters; - inherit (builtins) elemAt concatStringsSep length elem filter foldl' concatLists floor deepSeq listToAttrs attrValues attrNames mapAttrs hasAttr sort; + inherit (builtins) elemAt concatStringsSep length elem filter foldl' concatLists floor deepSeq listToAttrs attrValues attrNames mapAttrs hasAttr; index = i: list: elemAt list i; index2d = {x, y}: m: m |> index y |> index x; @@ -52,18 +52,18 @@ in if index2d fwd init.chart == "#" - then {pos = null; dist = null;} + then null else - if (isCorner fwd || fwd == init.goal || fwd == init.pos) + if (isNode fwd || fwd == init.goal || fwd == init.pos) then { - pos = (key fwd); - dist = score + 1; + name = (key fwd); + value = score + 1; } else search {pos = fwd; inherit dir; score = score + 1;} ; - isCorner = pos: index2d pos init.chart == "." && + isNode = pos: index2d pos init.chart == "." && (dirs |> mapAttrs (name: dir: let n = addVec pos dir; in index2d n init.chart == ".") |> ({north, east, south, west}: @@ -72,156 +72,126 @@ key = {x, y}: "${toString (y + 1)},${toString (x + 1)}"; - corners = range 1 (init.width - 3) |> map (x: + nodes = range 1 (init.width - 3) |> map (x: range 1 (init.height - 3) |> map (y: {inherit x y;}) ) |> concatLists - |> filter isCorner + |> filter isNode |> (nodes: nodes ++ [init.pos init.goal]) |> map (pos: { name = key pos; - value = ({ + value = { north = search {inherit pos; dir = dirs.north;}; east = search {inherit pos; dir = dirs.east;}; south = search {inherit pos; dir = dirs.south;}; west = search {inherit pos; dir = dirs.west;}; - } |> lib.filterAttrs (n: v: !isNull v.pos)); + } |> lib.filterAttrs (n: v: !isNull v); }) |> listToAttrs; - notNull = value: !isNull value; - - nodeGraph = graph: pkgs.runCommand "graph" {} '' + graph = pkgs.runCommand "graph" {} '' mkdir -p $out echo 'digraph { - ${graph |> mapAttrs (from: edges: - edges |> mapAttrs (dir: edge: if isNull edge.pos or null then "" else '' - "${from}" -> "${edge.pos}" [label="${dir} ${toString edge.dist}"] + ${nodes |> mapAttrs (from: tos: + tos |> mapAttrs (dir: edge: if isNull edge then "" else '' + "${from}" -> "${edge.name}" [label="${dir} ${toString edge.value}"] '') |> attrValues |> concatStringsSep "\n" ) |> attrValues |> concatStringsSep "\n"} }' > $out/graph.dot cat $out/graph.dot | ${pkgs.graphviz}/bin/dot -Tsvg > $out/graph.svg ''; - graphs = { - corners = nodeGraph corners; - }; + getPath = { + pos ? key init.pos + , goal + , dir ? "east" + , score ? 0 + , hist ? [] + , acc ? {} + }: let + node = nodes.${pos}; + in attrNames node |> foldl' (best: edgedir: + lib.traceSeq acc + (let + newHist = hist ++ [edge.name value]; + edge = node.${edgedir}; + turnCost = if edgedir == dir then 0 else 1000; + value = edge.value + turnCost; + newScore = score + value; + in + if isNull edge || elem edge.name hist || (hasAttr "score" best && newScore > best.score) then best else + if edge.name == goal && (hasAttr "score" best -> newScore < best.score) then + ({score = newScore;}) + else + getPath { + pos = edge.name; + dir = edgedir; + score = newScore; + hist = newHist; + acc = best; + inherit goal; + } + )) acc; initScores = { - ${key init.pos} = { - dir = "east"; - steps = 0; - dist = 0; - turns = 0; - pos = key init.pos; - }; + scores = mapAttrs (n: v: + if n == key init.pos then {dir = "east"; score = 0; pos = n;} + else {dir = null; score = null; pos = n;} + ) nodes; + done = []; }; - getLowest = state: let + getScores = state: let unvisited = removeAttrs state.scores state.done; + top = attrValues unvisited |> foldl' (acc: node: + if isNull acc.score then node else + if isNull node.score then acc else + if acc.score < node.score then acc else node + ) {score = null;}; in - attrValues unvisited |> foldl' (acc: node: - if isNull acc.turns then node else - if isNull node.turns then acc else - if acc.turns < node.turns then acc else node - ) {turns = null; steps = null;} - ; - - getScores = {done ? [], scores ? initScores}: let - prev = getLowest {inherit done scores;}; - in - corners.${prev.pos} - |> mapAttrs (dir: edge: let - turns = if prev.dir == dir then prev.turns else prev.turns + 1; - existing = scores.${edge.pos} or null; - steps = prev.steps + edge.dist; - # can't evaluate same turns - # because we don't know where we will turn next. - sameSteps = notNull existing - && existing.steps == steps - ; - isBetter = isNull existing || - (existing.steps > steps); - newScore = { - inherit steps dir turns; - inherit (edge) pos dist; - prev = prev.pos; - }; + nodes.${top.pos} + |> mapAttrs (dir: {name, value}: let + turnCost = if top.dir == dir then 0 else 1000; + existing = state.scores.${name}; + score = top.score + value + turnCost; + isBetter = isNull existing.score || existing.score > score; in { - name = edge.pos; - value = if sameSteps then - existing // {alt = newScore;} - else if isBetter - then newScore - else existing - ; + inherit name; + value = if elem name state.done || !isBetter then existing else { + inherit dir score; + pos = name; + }; }) |> attrValues |> listToAttrs |> (newScores: { - scores = scores // newScores; - done = done ++ [prev.pos]; + scores = state.scores // newScores; + done = state.done ++ [top.pos]; }) ; - fastest = goal: s: let + getPriority = {scores, done}: scores + |> lib.attrsToList + |> filter ({name, ...}: !elem name done) + |> filter ({value, ...}: !isNull value.score) + |> sortQueue + ; + + lessThan = a: b: isNull a || isNull b || a < b; + + sortQueue = q: q + |> builtins.sort (a: b: lessThan a.value.score b.value.score) + # |> (q: lib.traceSeq (map (v: v.value.score) q) q) + |> map ({name, value}: { + pos = name; + inherit (value) dir score; + }) + ; + + fastest = s: let next = getScores s; - in if elem (key goal) (next.done) then next else fastest goal next; - - pathScore = steps: turns: steps + turns * 1000; - - toGoal = (fastest init.goal {}).scores.${key init.goal}; - - part1result = pathScore toGoal.steps toGoal.turns; - -# PART 2 - - # WIP: - # go through the path, add each pos to visited - # if point has alt, - # TODO: and the alt turns add up to the correct path. - # find points on that path. - # add length of filtered alt, (-1 for the overlap at end?). - # add alt to visited. - pathToList = scores: {pos ? key init.goal, visited ? []}: - let - res = scores.${pos}; - prevPath = if res ? prev - then pathToList scores {pos = res.prev; visited = visited;} - else {path = []; visited = [pos];}; - a = pathToList scores {pos = res.alt.prev; visited = visited ++ prevPath.visited;}; - altPath = if res ? alt - then {path = [thisAltSpot] ++ a.path; inherit (a) visited;} - else {path = []; visited = [];} - ; - - thisSpot = {inherit (res) pos steps turns dir dist; - alt = altPath.path; - }; - thisAltSpot = {inherit (res.alt) pos steps turns dir dist; - alt = []; - }; - - in { - visited = [pos] ++ prevPath.visited ++ altPath.visited; - path = - if elem pos visited then [] else - [thisSpot] - ++ - prevPath.path - ; - }; - - cornerScores = fastest init.goal {}; - - addDist = foldl' (acc: {dist,alt,dir,...}: - acc + dist + (if addDist alt == 1 then 0 else (addDist alt) - 2) - ) 1; - - part1path = (pathToList cornerScores.scores {}); - -# not 520, too high - part2resultWrong = addDist part1path.path; + in if elem (key init.goal) next.done then next else fastest next; + part1result = (fastest initScores).scores.${key init.goal}; } diff --git a/2024/18/example.txt b/2024/18/example.txt deleted file mode 100644 index 79c8583..0000000 --- a/2024/18/example.txt +++ /dev/null @@ -1,25 +0,0 @@ -5,4 -4,2 -4,5 -3,0 -2,1 -6,3 -2,4 -1,5 -0,6 -3,3 -2,6 -5,1 -1,2 -5,5 -2,5 -6,5 -1,4 -0,4 -6,4 -1,1 -6,1 -1,0 -0,5 -1,6 -2,0 diff --git a/2024/18/solution.nix b/2024/18/solution.nix deleted file mode 100644 index 65897eb..0000000 --- a/2024/18/solution.nix +++ /dev/null @@ -1,52 +0,0 @@ -{lib, input ? "", my-lib, ...}@pkgs: rec { - inherit pkgs; - inherit lib; - inherit (builtins) elemAt genList elem concatStringsSep; - inherit (lib) splitString; - inherit (lib.strings) toIntBase10; - - init = input: input |> lib.trim - |> splitString "\n" - |> map (line: let c = splitString "," line; in { - x = elemAt c 0 |> toIntBase10; - y = elemAt c 1 |> toIntBase10; - }) - ; - - genChart = size: coords: genList (y: genList (x: - if x == 0 && y == 0 then "S" else - if x == size - 1 && y == size - 1 then "E" else - if elem {inherit x y;} coords then "#" else "." - ) size) size; - - chartToStr = chart: chart |> (map (concatStringsSep "")) |> concatStringsSep "\n"; - - mkSolution = {size, bytes, input}: rec { - inherit size bytes input; - walls = lib.sublist 0 bytes (init input); - chart = genChart size walls; - part1result = dijkstra.toGoal.steps; - dijkstra = my-lib.dijkstra { - pos = {x = 0; y = 0;}; - goal = {x = size - 1; y = size - 1;}; - chart = chart; - width = size; - height = size; - }; - }; - - example = mkSolution { - size = 7; - bytes = 12; - input = lib.readFile ./example.txt; - }; - - real = mkSolution { - size = 71; - bytes = 1024; - input = input; - }; - - # not 272, too high - -} diff --git a/2024/flake.lock b/2024/flake.lock index 428cf6f..fa45999 100644 --- a/2024/flake.lock +++ b/2024/flake.lock @@ -3,8 +3,8 @@ "aoc-inputs": { "flake": false, "locked": { - "lastModified": 1734543082, - "narHash": "sha256-hU8vRkPOKfQqp34Ffx4INiz3l6HdwGcbfyHgqO4qheM=", + "lastModified": 1734334657, + "narHash": "sha256-NjsEC/6Mu+i94YPgK4yN0Y5TxhlWwXK5Ev2UzIkfGZo=", "path": "/tmp/aoc-inputs", "type": "path" }, diff --git a/2024/flake.nix b/2024/flake.nix index 51a7592..67dfb0b 100644 --- a/2024/flake.nix +++ b/2024/flake.nix @@ -29,16 +29,12 @@ |> map (i: let id = lib.fixedWidthNumber 2 i; in { name = "day-${id}"; value = let - solution = import ./${id}/solution.nix ( pkgs // { - my-lib = import ./lib/. pkgs; - inherit input; - } ); + solution = import ./${id}/solution.nix pkgs; example = (pkgs.lib.readFile ./${id}/example.txt); example2 = (pkgs.lib.readFile ./${id}/example2.txt); example3 = (pkgs.lib.readFile ./${id}/example3.txt); input = (pkgs.lib.readFile "${aoc-inputs}/${id}"); in { - inherit solution; example = solution example; example2 = solution example2; example3 = solution example3; diff --git a/2024/graph.jpg b/2024/graph.jpg new file mode 100644 index 0000000..86e215e Binary files /dev/null and b/2024/graph.jpg differ diff --git a/2024/lib/default.nix b/2024/lib/default.nix deleted file mode 100644 index 4649f02..0000000 --- a/2024/lib/default.nix +++ /dev/null @@ -1,3 +0,0 @@ -pkgs: { - dijkstra = import ./dijkstra.nix pkgs; -} diff --git a/2024/lib/dijkstra.nix b/2024/lib/dijkstra.nix deleted file mode 100644 index 28b32da..0000000 --- a/2024/lib/dijkstra.nix +++ /dev/null @@ -1,212 +0,0 @@ -{lib, pkgs, ...}: {pos, goal, chart, width, height}@init: rec { - inherit lib; - inherit (lib) splitString mod range; - inherit (lib.lists) findFirstIndex imap0; - inherit (lib.strings) stringToCharacters; - inherit (builtins) elemAt concatStringsSep length elem filter foldl' concatLists floor deepSeq listToAttrs attrValues attrNames mapAttrs hasAttr sort genList; - - index = i: list: if isNull list || i < 0 || i >= length list then null else elemAt list i; - index2d = {x, y}: m: m |> index y |> index x; - - chartToStr = chart: chart |> (map (concatStringsSep "")) |> concatStringsSep "\n"; - - rotate' = {x, y}: {y = -x; x = y;}; - rotate = {x, y}: {y = x; x = -y;}; - - addVec = a: b: {x = a.x or 0 + b.x or 0; y = a.y or 0 + b.y or 0;}; - multVec = a: m: {x = a.x or 0 * m; y = a.y or 0 * m;}; - - - dirs = { - north = {x = 0; y = -1;}; - east = {x = 1; y = 0;}; - south = {x = 0; y = 1;}; - west = {x = -1; y = 0;}; - }; - - search = { - pos, - dir, - score ? 0, - }: let - fwd = addVec pos dir; - in - if - elem (index2d fwd init.chart) [null "#"] - then {pos = null; dist = null;} - else - if (isCorner fwd || fwd == init.goal || fwd == init.pos) - then { - pos = (key fwd); - dist = score + 1; - } - else - search {pos = fwd; inherit dir; score = score + 1;} - ; - - isCorner = pos: index2d pos init.chart == "." && - (dirs - |> mapAttrs (name: dir: let n = addVec pos dir; in index2d n init.chart == ".") - |> ({north, east, south, west}: - north && east || east && south || south && west || west && north)) - ; - - key = {x, y}: "${toString (y + 1)},${toString (x + 1)}"; - - corners = range 0 (init.width - 1) |> map (x: - range 0 (init.height - 1) |> map (y: {inherit x y;}) - ) - |> concatLists - |> filter isCorner - |> (nodes: nodes ++ [init.pos init.goal]) - |> map (pos: { - name = key pos; - value = ({ - north = search {inherit pos; dir = dirs.north;}; - east = search {inherit pos; dir = dirs.east;}; - south = search {inherit pos; dir = dirs.south;}; - west = search {inherit pos; dir = dirs.west;}; - } |> lib.filterAttrs (n: v: !isNull v.pos)); - }) - |> listToAttrs; - - notNull = value: !isNull value; - - nodeGraph = graph: pkgs.runCommand "graph" {} '' - mkdir -p $out - echo 'digraph { - ${graph |> mapAttrs (from: edges: - edges |> mapAttrs (dir: edge: if isNull edge.pos or null then "" else '' - "${from}" -> "${edge.pos}" [label="${dir} ${toString edge.dist}"] - '') |> attrValues |> concatStringsSep "\n" - ) |> attrValues |> concatStringsSep "\n"} - }' > $out/graph.dot - cat $out/graph.dot | ${pkgs.graphviz}/bin/dot -Tsvg > $out/graph.svg - ''; - - graphs = { - corners = nodeGraph corners; - }; - - initScores = { - ${key init.pos} = { - dir = "east"; - steps = 0; - dist = 0; - turns = 0; - pos = key init.pos; - }; - }; - - getLowest = state: let - unvisited = removeAttrs state.scores state.done; - in - attrValues unvisited |> foldl' (acc: node: - if isNull acc.steps then node else - if isNull node.steps then acc else - if acc.steps < node.steps then acc else node - ) {turns = null; steps = null;} - ; - - getScores = {done ? [], scores ? initScores}: let - prev = getLowest {inherit done scores;}; - in - corners.${prev.pos} - |> mapAttrs (dir: edge: let - turns = if prev.dir == dir then prev.turns else prev.turns + 1; - existing = scores.${edge.pos} or null; - steps = prev.steps + edge.dist; - # can't evaluate same turns - # because we don't know where we will turn next. - sameSteps = notNull existing - && existing.steps == steps - ; - isBetter = isNull existing || - (existing.steps > steps); - newScore = { - inherit steps dir turns; - inherit (edge) pos dist; - prev = prev.pos; - }; - in { - name = edge.pos; - value = if sameSteps then - existing // {alt = newScore;} - else if isBetter - then newScore - else existing - ; - }) - |> attrValues - |> listToAttrs - |> (newScores: { - scores = scores // newScores; - done = done ++ [prev.pos]; - }) - ; - - fastest = goal: s: let - next = getScores s; - in if elem (key goal) (next.done) then next else fastest goal next; - - pathScore = steps: turns: steps + turns * 1000; - - toGoal = (fastest init.goal {}).scores.${key init.goal}; - - part1result = pathScore toGoal.steps toGoal.turns; - -# PART 2 - - # WIP: - # go through the path, add each pos to visited - # if point has alt, - # TODO: and the alt turns add up to the correct path. - # find points on that path. - # add length of filtered alt, (-1 for the overlap at end?). - # add alt to visited. - pathToList = scores: {pos ? key init.goal, visited ? []}: - let - res = scores.${pos}; - prevPath = if res ? prev - then pathToList scores {pos = res.prev; visited = visited;} - else {path = []; visited = [pos];}; - a = pathToList scores {pos = res.alt.prev; visited = visited ++ prevPath.visited;}; - altPath = if res ? alt - then {path = [thisAltSpot] ++ a.path; inherit (a) visited;} - else {path = []; visited = [];} - ; - - thisSpot = {inherit (res) pos steps turns dir dist; - # alt = altPath.path; - }; - thisAltSpot = {inherit (res.alt) pos steps turns dir dist; - alt = []; - }; - - in { - visited = [pos] ++ prevPath.visited ++ altPath.visited; - path = - if elem pos visited then [] else - [thisSpot] - ++ - prevPath.path - ; - }; - - cornerScores = fastest init.goal {}; - - addDist = foldl' (acc: {dist,alt,dir,...}: - acc + dist + (if addDist alt == 1 then 0 else (addDist alt) - 2) - ) 1; - - part1path = (pathToList cornerScores.scores {}); - -# not 520, too high - part2resultWrong = addDist part1path.path; - - genChart = size: walls: path: genList (y: genList (x: - if elem {inherit x y;} walls then "#" - else if elem (key {inherit x y;}) (map ({pos,...}: pos) path) then "O" - else "." - ) size) size; -} diff --git a/2024/result b/2024/result new file mode 120000 index 0000000..d03bff3 --- /dev/null +++ b/2024/result @@ -0,0 +1 @@ +/nix/store/3dxw74x2scq38j9m84r6m3iq320npwn4-graph \ No newline at end of file