Compare commits

...

4 commits

Author SHA1 Message Date
Tristan 5467ea9393 2024 day 18 part 1 2024-12-18 19:38:22 +00:00
Tristan dbe990e782 2024 day 16 part 2 not working 2024-12-18 16:51:57 +00:00
Tristan 4cc73313ae 2024 day 16 remove borked code 2024-12-17 15:01:03 +00:00
Tristan a3e9984d4b 2024 day 16 struggles 2024-12-17 14:58:14 +00:00
9 changed files with 421 additions and 96 deletions

View file

@ -3,7 +3,7 @@
inherit (lib) splitString mod range; inherit (lib) splitString mod range;
inherit (lib.lists) findFirstIndex imap0; inherit (lib.lists) findFirstIndex imap0;
inherit (lib.strings) stringToCharacters; 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; index = i: list: elemAt list i;
index2d = {x, y}: m: m |> index y |> index x; index2d = {x, y}: m: m |> index y |> index x;
@ -52,18 +52,18 @@
in in
if if
index2d fwd init.chart == "#" index2d fwd init.chart == "#"
then null then {pos = null; dist = null;}
else else
if (isNode fwd || fwd == init.goal || fwd == init.pos) if (isCorner fwd || fwd == init.goal || fwd == init.pos)
then { then {
name = (key fwd); pos = (key fwd);
value = score + 1; dist = score + 1;
} }
else else
search {pos = fwd; inherit dir; score = score + 1;} search {pos = fwd; inherit dir; score = score + 1;}
; ;
isNode = pos: index2d pos init.chart == "." && isCorner = pos: index2d pos init.chart == "." &&
(dirs (dirs
|> mapAttrs (name: dir: let n = addVec pos dir; in index2d n init.chart == ".") |> mapAttrs (name: dir: let n = addVec pos dir; in index2d n init.chart == ".")
|> ({north, east, south, west}: |> ({north, east, south, west}:
@ -72,126 +72,156 @@
key = {x, y}: "${toString (y + 1)},${toString (x + 1)}"; 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;}) range 1 (init.height - 3) |> map (y: {inherit x y;})
) )
|> concatLists |> concatLists
|> filter isNode |> filter isCorner
|> (nodes: nodes ++ [init.pos init.goal]) |> (nodes: nodes ++ [init.pos init.goal])
|> map (pos: { |> map (pos: {
name = key pos; name = key pos;
value = { value = ({
north = search {inherit pos; dir = dirs.north;}; north = search {inherit pos; dir = dirs.north;};
east = search {inherit pos; dir = dirs.east;}; east = search {inherit pos; dir = dirs.east;};
south = search {inherit pos; dir = dirs.south;}; south = search {inherit pos; dir = dirs.south;};
west = search {inherit pos; dir = dirs.west;}; west = search {inherit pos; dir = dirs.west;};
} |> lib.filterAttrs (n: v: !isNull v); } |> lib.filterAttrs (n: v: !isNull v.pos));
}) })
|> listToAttrs; |> listToAttrs;
graph = pkgs.runCommand "graph" {} '' notNull = value: !isNull value;
nodeGraph = graph: pkgs.runCommand "graph" {} ''
mkdir -p $out mkdir -p $out
echo 'digraph { echo 'digraph {
${nodes |> mapAttrs (from: tos: ${graph |> mapAttrs (from: edges:
tos |> mapAttrs (dir: edge: if isNull edge then "" else '' edges |> mapAttrs (dir: edge: if isNull edge.pos or null then "" else ''
"${from}" -> "${edge.name}" [label="${dir} ${toString edge.value}"] "${from}" -> "${edge.pos}" [label="${dir} ${toString edge.dist}"]
'') |> attrValues |> concatStringsSep "\n" '') |> attrValues |> concatStringsSep "\n"
) |> attrValues |> concatStringsSep "\n"} ) |> attrValues |> concatStringsSep "\n"}
}' > $out/graph.dot }' > $out/graph.dot
cat $out/graph.dot | ${pkgs.graphviz}/bin/dot -Tsvg > $out/graph.svg cat $out/graph.dot | ${pkgs.graphviz}/bin/dot -Tsvg > $out/graph.svg
''; '';
getPath = { graphs = {
pos ? key init.pos corners = nodeGraph corners;
, 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 = [];
}; };
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; 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 in
nodes.${top.pos} attrValues unvisited |> foldl' (acc: node:
|> mapAttrs (dir: {name, value}: let if isNull acc.turns then node else
turnCost = if top.dir == dir then 0 else 1000; if isNull node.turns then acc else
existing = state.scores.${name}; if acc.turns < node.turns then acc else node
score = top.score + value + turnCost; ) {turns = null; steps = null;}
isBetter = isNull existing.score || existing.score > score; ;
in {
inherit name; getScores = {done ? [], scores ? initScores}: let
value = if elem name state.done || !isBetter then existing else { prev = getLowest {inherit done scores;};
inherit dir score; in
pos = name; 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 |> attrValues
|> listToAttrs |> listToAttrs
|> (newScores: { |> (newScores: {
scores = state.scores // newScores; scores = scores // newScores;
done = state.done ++ [top.pos]; done = done ++ [prev.pos];
}) })
; ;
getPriority = {scores, done}: scores fastest = goal: s: let
|> 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; 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};
} }

25
2024/18/example.txt Normal file
View file

@ -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

52
2024/18/solution.nix Normal file
View file

@ -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
}

View file

@ -3,8 +3,8 @@
"aoc-inputs": { "aoc-inputs": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1734334657, "lastModified": 1734543082,
"narHash": "sha256-NjsEC/6Mu+i94YPgK4yN0Y5TxhlWwXK5Ev2UzIkfGZo=", "narHash": "sha256-hU8vRkPOKfQqp34Ffx4INiz3l6HdwGcbfyHgqO4qheM=",
"path": "/tmp/aoc-inputs", "path": "/tmp/aoc-inputs",
"type": "path" "type": "path"
}, },

View file

@ -29,12 +29,16 @@
|> map (i: let id = lib.fixedWidthNumber 2 i; in { |> map (i: let id = lib.fixedWidthNumber 2 i; in {
name = "day-${id}"; name = "day-${id}";
value = let 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); example = (pkgs.lib.readFile ./${id}/example.txt);
example2 = (pkgs.lib.readFile ./${id}/example2.txt); example2 = (pkgs.lib.readFile ./${id}/example2.txt);
example3 = (pkgs.lib.readFile ./${id}/example3.txt); example3 = (pkgs.lib.readFile ./${id}/example3.txt);
input = (pkgs.lib.readFile "${aoc-inputs}/${id}"); input = (pkgs.lib.readFile "${aoc-inputs}/${id}");
in { in {
inherit solution;
example = solution example; example = solution example;
example2 = solution example2; example2 = solution example2;
example3 = solution example3; example3 = solution example3;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

3
2024/lib/default.nix Normal file
View file

@ -0,0 +1,3 @@
pkgs: {
dijkstra = import ./dijkstra.nix pkgs;
}

212
2024/lib/dijkstra.nix Normal file
View file

@ -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;
}

View file

@ -1 +0,0 @@
/nix/store/3dxw74x2scq38j9m84r6m3iq320npwn4-graph