diff --git a/2024/16/solution.nix b/2024/16/solution.nix index 452760f..a5adb3a 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; + inherit (builtins) elemAt concatStringsSep length elem filter foldl' concatLists floor deepSeq listToAttrs attrValues attrNames mapAttrs hasAttr sort; 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 null + then {pos = null; dist = null;} else - if (isNode fwd || fwd == init.goal || fwd == init.pos) + if (isCorner fwd || fwd == init.goal || fwd == init.pos) then { - name = (key fwd); - value = score + 1; + pos = (key fwd); + dist = score + 1; } else search {pos = fwd; inherit dir; score = score + 1;} ; - isNode = pos: index2d pos init.chart == "." && + isCorner = pos: index2d pos init.chart == "." && (dirs |> mapAttrs (name: dir: let n = addVec pos dir; in index2d n init.chart == ".") |> ({north, east, south, west}: @@ -72,126 +72,156 @@ key = {x, y}: "${toString (y + 1)},${toString (x + 1)}"; - nodes = range 1 (init.width - 3) |> map (x: + corners = range 1 (init.width - 3) |> map (x: range 1 (init.height - 3) |> map (y: {inherit x y;}) ) |> concatLists - |> filter isNode + |> filter isCorner |> (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); + } |> lib.filterAttrs (n: v: !isNull v.pos)); }) |> listToAttrs; - graph = pkgs.runCommand "graph" {} '' + notNull = value: !isNull value; + + nodeGraph = graph: pkgs.runCommand "graph" {} '' mkdir -p $out echo 'digraph { - ${nodes |> mapAttrs (from: tos: - tos |> mapAttrs (dir: edge: if isNull edge then "" else '' - "${from}" -> "${edge.name}" [label="${dir} ${toString edge.value}"] + ${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 ''; - 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 = { - 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 = []; + graphs = { + corners = nodeGraph corners; }; - getScores = state: let + 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; - 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 - 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 { - inherit name; - value = if elem name state.done || !isBetter then existing else { - inherit dir score; - pos = name; + 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; }; + in { + name = edge.pos; + value = if sameSteps then + existing // {alt = newScore;} + else if isBetter + then newScore + else existing + ; }) |> attrValues |> listToAttrs |> (newScores: { - scores = state.scores // newScores; - done = state.done ++ [top.pos]; + scores = scores // newScores; + done = done ++ [prev.pos]; }) ; - 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 + fastest = goal: s: let next = getScores s; - in if elem (key init.goal) next.done then next else fastest next; + 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; - part1result = (fastest initScores).scores.${key init.goal}; } diff --git a/2024/18/example.txt b/2024/18/example.txt new file mode 100644 index 0000000..79c8583 --- /dev/null +++ b/2024/18/example.txt @@ -0,0 +1,25 @@ +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 new file mode 100644 index 0000000..65897eb --- /dev/null +++ b/2024/18/solution.nix @@ -0,0 +1,52 @@ +{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 fa45999..428cf6f 100644 --- a/2024/flake.lock +++ b/2024/flake.lock @@ -3,8 +3,8 @@ "aoc-inputs": { "flake": false, "locked": { - "lastModified": 1734334657, - "narHash": "sha256-NjsEC/6Mu+i94YPgK4yN0Y5TxhlWwXK5Ev2UzIkfGZo=", + "lastModified": 1734543082, + "narHash": "sha256-hU8vRkPOKfQqp34Ffx4INiz3l6HdwGcbfyHgqO4qheM=", "path": "/tmp/aoc-inputs", "type": "path" }, diff --git a/2024/flake.nix b/2024/flake.nix index 67dfb0b..51a7592 100644 --- a/2024/flake.nix +++ b/2024/flake.nix @@ -29,12 +29,16 @@ |> map (i: let id = lib.fixedWidthNumber 2 i; in { name = "day-${id}"; value = let - solution = import ./${id}/solution.nix pkgs; + solution = import ./${id}/solution.nix ( pkgs // { + my-lib = import ./lib/. pkgs; + inherit input; + } ); 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 deleted file mode 100644 index 86e215e..0000000 Binary files a/2024/graph.jpg and /dev/null differ diff --git a/2024/lib/default.nix b/2024/lib/default.nix new file mode 100644 index 0000000..4649f02 --- /dev/null +++ b/2024/lib/default.nix @@ -0,0 +1,3 @@ +pkgs: { + dijkstra = import ./dijkstra.nix pkgs; +} diff --git a/2024/lib/dijkstra.nix b/2024/lib/dijkstra.nix new file mode 100644 index 0000000..28b32da --- /dev/null +++ b/2024/lib/dijkstra.nix @@ -0,0 +1,212 @@ +{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 deleted file mode 120000 index d03bff3..0000000 --- a/2024/result +++ /dev/null @@ -1 +0,0 @@ -/nix/store/3dxw74x2scq38j9m84r6m3iq320npwn4-graph \ No newline at end of file