From fc2cde6509d4dae9de4c4f36932c6e86282de9b1 Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Fri, 20 Dec 2024 21:04:30 -0700 Subject: [PATCH 01/10] Day 20. Took a while to figure it out but it was a fun one --- day20/main.exs | 112 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 day20/main.exs diff --git a/day20/main.exs b/day20/main.exs new file mode 100644 index 0000000..3d99c3d --- /dev/null +++ b/day20/main.exs @@ -0,0 +1,112 @@ +defmodule Day20 do + def run do + {map, width, height, start, dest} = parse_input("data.txt") + + # create a map of visited nodes an a queue + visited = %{start => 100_000_000} + q = :queue.from_list([{start, 0, []}]) + + {path, shortest} = walk(map, q, dest, visited, [], 100_000_000_000, width, height) + path = Enum.reverse(path) + + part1 = run(2, path) + part2 = run(20, path) + dbg({part1, part2}) + end + + # walk every step along the path + # find coordinates that we can get to within number of steps, that save us 100 picoseconds + defp run(cheats, path) do + Enum.with_index(path) + |> Enum.reduce(0, fn {el, idx}, acc -> + res = + path + |> Enum.with_index() + |> Enum.filter(fn {el2, idx2} -> + dist = distance(el, el2) + dist <= cheats and idx + dist + 100 <= idx2 + end) + |> length + + acc + res + end) + end + + defp distance({x1, y1}, {x2, y2}) do + abs(x2 - x1) + abs(y2 - y1) + end + + defp walk(map, q, {dx, dy} = dest, visited, paths, shortest_path, width, height) do + # dequeue the node with the lowest distnce + # no priority queue, so just find the lowest one and delete it from the queue + ql = :queue.to_list(q) + + if length(ql) == 0 do + nil + else + # lowest = next_from_queue(ql, dest) + lowest = Enum.min_by(ql, fn {_, dist, _} -> dist end) + {{x, y}, dist, path} = lowest + new_q = :queue.delete(lowest, q) + + # if we reached the destination + if x == dx and y == dy do + {[{x, y} | path], dist} + else + dirs = [{1, 0}, {0, 1}, {-1, 0}, {0, -1}] + + # find neighbors to enqueue + neighbors = + dirs + |> Enum.map(fn {dx, dy} -> {x + dx, y + dy} end) + |> Enum.map(fn key -> {key, Map.get(map, key, ".")} end) + |> Enum.filter(fn {{x, y}, el} -> + el != "#" and x >= 0 and y >= 0 and x < width and y < height + end) + |> Enum.reject(fn {{nx, ny}, _el} -> Map.has_key?(visited, {nx, ny}) end) + |> Enum.reject(fn {{nx, ny}, _el} -> + Enum.any?(:queue.to_list(q), fn {pos, _, _} -> pos == {nx, ny} end) + end) + |> Enum.map(fn {key, _el} -> key end) + + new_q2 = + Enum.reduce(neighbors, new_q, fn {nx, ny}, acc -> + :queue.in({{nx, ny}, dist + 1, [{x, y} | path]}, acc) + end) + + new_visited = Map.put(visited, {x, y}, dist) + walk(map, new_q2, dest, new_visited, paths, shortest_path, width, height) + end + end + end + + defp parse_input(file) do + map_list = + File.read!(file) + |> String.trim() + |> String.split("\n") + |> Enum.map(&String.graphemes/1) + + height = length(map_list) + width = length(hd(map_list)) + + # convert the map List to a map Map, grabbing start and dest along the way + {map, start, dest} = + Enum.concat(map_list) + |> Enum.with_index() + |> Enum.reduce({%{}, nil, nil}, fn {el, idx}, {map, start, dest} -> + row = div(idx, height) + col = rem(idx, width) + + map = Map.put(map, {row, col}, el) + start = if el == "S", do: {row, col}, else: start + dest = if el == "E", do: {row, col}, else: dest + + {map, start, dest} + end) + + {map, width, height, start, dest} + end +end + +Day20.run() From dc0f6144a93652b32ee5ad760201def11999407b Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Wed, 25 Dec 2024 16:41:44 -0700 Subject: [PATCH 02/10] Day 21. Man these are getting hard --- day21/main.exs | 181 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 day21/main.exs diff --git a/day21/main.exs b/day21/main.exs new file mode 100644 index 0000000..5044ae6 --- /dev/null +++ b/day21/main.exs @@ -0,0 +1,181 @@ +defmodule Day21 do + def run do + lines = + File.read!("data.txt") + |> String.trim() + |> String.split("\n") + |> Enum.map(&String.graphemes/1) + + numpad = %{ + nil => {0, 0}, + "0" => {1, 0}, + "A" => {2, 0}, + "1" => {0, 1}, + "2" => {1, 1}, + "3" => {2, 1}, + "4" => {0, 2}, + "5" => {1, 2}, + "6" => {2, 2}, + "7" => {0, 3}, + "8" => {1, 3}, + "9" => {2, 3} + } + + dpad = %{ + "<" => {0, 0}, + "v" => {1, 0}, + ">" => {2, 0}, + nil => {0, 1}, + "^" => {1, 1}, + "A" => {2, 1} + } + + # doing some testing... + # go([[">", "v"], ["v", ">"]], dpad) |> go(dpad) |> dbg + # go([["<", "v"], ["v", "<"]], dpad) |> go(dpad) |> dbg + # go([[">", "^"], ["^", ">"]], dpad) |> go(dpad) |> dbg + # go([["<", "^"], ["^", "<"]], dpad) |> go(dpad) |> dbg + # reveals that: + # <, v is better than v, < + # v, > is better than >, v + # >, ^ is better than ^, > + # <, ^ is better than ^, < + + part1 = + lines + |> go(numpad) + |> go(dpad) + |> go(dpad) + |> Enum.zip(lines) + |> Enum.map(fn {sequence, line} -> + numeric_part = + line |> Enum.drop(-1) |> Enum.map(&String.to_integer/1) |> Integer.undigits() + + numeric_part * length(sequence) + end) + |> Enum.sum() + + dbg(part1, charlists: :as_lists) + + part2 = + lines + |> go(numpad) + |> Enum.zip(lines) + # for each line + |> Enum.reduce({%{}, 0}, fn {line, original_line}, {cache, sum} -> + # for each key + {new_cache, _, len} = + Enum.reduce(line, {cache, "A", 0}, fn key, {cache, previous, sum} -> + {num, cache} = num_steps_with_depth(previous, key, dpad, 25, cache) + {cache, key, sum + num} + end) + + numeric_part = + original_line |> Enum.drop(-1) |> Enum.map(&String.to_integer/1) |> Integer.undigits() + + {new_cache, sum + numeric_part * len} + end) + + dbg(part2) + end + + # finds the number of steps needed to get from a to b, over and over, depth times + defp num_steps_with_depth(a, b, keypad, depth, cache) do + key = {a, b, depth} + + if Map.has_key?(cache, key) do + res = Map.get(cache, key) + {res, cache} + else + new_steps = steps(a, b, keypad) + + case depth do + 0 -> + new_cache = Map.put(cache, key, 2) + {2, new_cache} + + 1 -> + len = length(new_steps) + new_cache = Map.put(cache, key, len) + {len, new_cache} + + d -> + # for each pair, find the length of depth - 1 + {new_cache, el, steps} = + Enum.reduce(new_steps, {cache, "A", 0}, fn el, {cache, previous, num_steps} -> + {num, new_cache} = num_steps_with_depth(previous, el, keypad, depth - 1, cache) + new_cache = Map.put(new_cache, {previous, el, depth - 1}, num) + {new_cache, el, num + num_steps} + end) + + {steps, new_cache} + end + end + end + + # maps over each line, then reduces over each keypress, gathering the new steps needed + defp go(lines, pad) do + Enum.map(lines, fn line -> + Enum.reduce(line, {"A", []}, fn el, {previous, list} -> + {el, Enum.concat(list, steps(previous, el, pad))} + end) + |> elem(1) + end) + end + + # returns the steps from a to b on the given keypad, including hitting A at the end + defp steps(a, b, keypad) do + {x1, y1} = Map.get(keypad, a) + {x2, y2} = Map.get(keypad, b) + + y_char = if y2 > y1, do: "^", else: "v" + x_char = if x2 > x1, do: ">", else: "<" + + xs = + if x2 == x1, + do: [], + else: Enum.reduce(1..abs(x2 - x1), [], fn _el, acc -> [x_char | acc] end) + + ys = + if y2 == y1, + do: [], + else: Enum.reduce(1..abs(y2 - y1), [], fn _el, acc -> [y_char | acc] end) + + {dx, dy} = Map.get(keypad, nil) + + # avoid hole + res = + if x2 < x1 do + # if i'm going left, and going left _first_ hits the empty spot + if {x2, y1} == {dx, dy} do + # then go up/down first + Enum.concat([ys, xs, ["A"]]) + end + else + # if i'm going up or down, and going up/down _first_ hits the empty spot + if y1 != y2 and {x1, y2} == {dx, dy} do + # then go left/right first + Enum.concat([xs, ys, ["A"]]) + end + end + + # if the hole needed to be avoided, return that + if !is_nil(res) do + res + else + # otherwise use our heuristic + cond do + # if we're going down and left, go left first + y2 < y1 and x2 < x1 -> Enum.concat([xs, ys, ["A"]]) + # if we're going down and right, go down first + y2 < y1 and x2 > x1 -> Enum.concat([ys, xs, ["A"]]) + # if we're going up and left, go left first + y2 > y1 and x2 < x1 -> Enum.concat([xs, ys, ["A"]]) + # if we're going up and right (or anything else), go up first + true -> Enum.concat([ys, xs, ["A"]]) + end + end + end +end + +Day21.run() From 2a3ca7c3938ff1169e95df6bfa8142623d08c7d4 Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Thu, 26 Dec 2024 13:55:11 -0700 Subject: [PATCH 03/10] Day 22. not done yet. My solution for part 2 a) doesn\'t work, and b) doesn\'t scale lol --- day22/main.exs | 131 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 day22/main.exs diff --git a/day22/main.exs b/day22/main.exs new file mode 100644 index 0000000..41cd0e9 --- /dev/null +++ b/day22/main.exs @@ -0,0 +1,131 @@ +defmodule Day22 do + def run do + lines = + File.read!("test.txt") + |> String.trim() + |> String.split("\n") + |> Enum.map(&String.to_integer/1) + + part1 = + Enum.reduce(lines, 0, fn secret, acc -> + acc + Enum.reduce(1..2000, secret, fn _, s -> evolve(s) end) + end) + + dbg(part1) + + # lines = [123, 123] + + {prices, diffs} = + Enum.reduce(lines, {[], []}, fn secret, {prices_acc, diffs_acc} -> + {_, prices, diffs} = + Enum.reduce(1..2000, {secret, [price(secret)], []}, fn _, {s, prices, diffs} -> + new_secret = evolve(s) + price = price(new_secret) + diff = price - hd(prices) + {new_secret, [price | prices], [diff | diffs]} + end) + + {[prices | prices_acc], [diffs | diffs_acc]} + end) + + dbg(prices) + dbg(diffs) + + concatted_diffs = Enum.concat(diffs) + + # iterate over ALL 4 diff sequences among all the numbers + part2 = + concatted_diffs + |> Enum.with_index() + |> Enum.reduce({0, []}, fn {d, idx}, {max, best_seq} -> + seq = Enum.slice(concatted_diffs, idx, 4) + # dbg({d, idx, seq}) + + if length(seq) == 4 do + # find the index of this 4 diff sequence in all the diff lists + total = + Enum.with_index(diffs) + |> Enum.reduce(0, fn {diff_list, list_idx}, acc -> + i = index_of_sublist(diff_list, seq) + # dbg({"looking for sublist", seq, i}) + + if Enum.reverse(seq) == [-2, 1, -1, 3] do + dbg("found seq") + dbg(seq) + dbg({"checking diff list for this seq", i}) + end + + # if this seq exists in this diff list, find the corresponding price + if !is_nil(i) do + # dbg(list_idx) + # dbg(prices) + price_list = Enum.at(prices, list_idx) + # dbg(price_list) + price = Enum.at(price_list, i) + + if Enum.reverse(seq) == [-2, 1, -1, 3] do + dbg({"price", price}) + end + + # dbg({"found the seq. price", price}) + acc + price + else + acc + end + end) + + if Enum.reverse(seq) == [-2, 1, -1, 3] do + dbg({"total total", total}) + end + + if total >= max do + dbg({"new winner", total, seq}) + {total, seq} + else + {max, best_seq} + end + else + {max, best_seq} + end + end) + + dbg(part2) + end + + defp evolve(secret) do + step1 = secret |> Kernel.*(64) |> mix(secret) |> prune() + step2 = step1 |> div(32) |> mix(step1) |> prune() + step2 |> Kernel.*(2048) |> mix(step2) |> prune() + end + + defp mix(num, secret) do + Bitwise.bxor(num, secret) + end + + defp prune(num) do + Integer.mod(num, 16_777_216) + end + + defp price(num) do + Integer.digits(num) |> Enum.take(-1) |> Integer.undigits() + end + + # finds the index of a sequence in a list, nil if it isn't in there + defp index_of_sublist(parent, sublist) do + sub_len = length(sublist) + + Enum.reduce(0..(length(parent) - sub_len), nil, fn idx, acc -> + if acc do + acc + else + slice = Enum.slice(parent, idx, sub_len) + + if slice == sublist do + idx + end + end + end) + end +end + +Day22.run() From 141e4761d3ee4c712c12f652d3d54771e6042cd2 Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Thu, 26 Dec 2024 19:51:51 -0700 Subject: [PATCH 04/10] Day 22. This solution also does not scale. I think I\'ll try a map or something next --- day22/main.exs | 118 ++++++++++++++++++++++++------------------------- 1 file changed, 57 insertions(+), 61 deletions(-) diff --git a/day22/main.exs b/day22/main.exs index 41cd0e9..b111b4d 100644 --- a/day22/main.exs +++ b/day22/main.exs @@ -1,7 +1,7 @@ defmodule Day22 do def run do lines = - File.read!("test.txt") + File.read!("data.txt") |> String.trim() |> String.split("\n") |> Enum.map(&String.to_integer/1) @@ -28,70 +28,74 @@ defmodule Day22 do {[prices | prices_acc], [diffs | diffs_acc]} end) - dbg(prices) - dbg(diffs) + dbg({prices, diffs}) - concatted_diffs = Enum.concat(diffs) + # max length of all price lists + max = Enum.map(prices, &length/1) |> Enum.max() - # iterate over ALL 4 diff sequences among all the numbers - part2 = - concatted_diffs + # sort the lists by price, keeping their original index + sorted_by_price_with_index = + Enum.map(prices, fn prices -> + prices |> Enum.with_index() |> Enum.sort(fn {p1, _}, {p2, _} -> p1 >= p2 end) + end) |> Enum.with_index() - |> Enum.reduce({0, []}, fn {d, idx}, {max, best_seq} -> - seq = Enum.slice(concatted_diffs, idx, 4) - # dbg({d, idx, seq}) - if length(seq) == 4 do - # find the index of this 4 diff sequence in all the diff lists - total = - Enum.with_index(diffs) - |> Enum.reduce(0, fn {diff_list, list_idx}, acc -> - i = index_of_sublist(diff_list, seq) - # dbg({"looking for sublist", seq, i}) + # dbg(sorted_by_price_with_index) - if Enum.reverse(seq) == [-2, 1, -1, 3] do - dbg("found seq") - dbg(seq) - dbg({"checking diff list for this seq", i}) - end + # go from 0 up to the max list length of the lists sorted by price, highest to lowest + # grab the 4-sequence of each list + # check each sequence against each list, add up the prices + # if that price is greater than the max acc price, that's the new winning sequence + part2 = + Enum.reduce(0..(max - 1), 0, fn index, acc -> + dbg({"checking all lists at index", index}) + dbg({"current acc", acc}) - # if this seq exists in this diff list, find the corresponding price - if !is_nil(i) do - # dbg(list_idx) - # dbg(prices) - price_list = Enum.at(prices, list_idx) - # dbg(price_list) - price = Enum.at(price_list, i) + res = + Enum.reduce(sorted_by_price_with_index, {0, []}, fn {price_list, list_index}, + {max_price, max_seq} -> + {price, original_index} = Enum.at(price_list, index) + # dbg({diffs, index}) + seq = Enum.at(diffs, list_index) |> Enum.slice(original_index, 4) + # dbg({price, seq}) - if Enum.reverse(seq) == [-2, 1, -1, 3] do - dbg({"price", price}) - end + if length(seq) == 4 do + max_price = score_seq(prices, diffs, seq) + # dbg({"max_price", max_price}) - # dbg({"found the seq. price", price}) - acc + price - else - acc - end - end) + {max_price, seq} + else + {max_price, max_seq} + end + end) - if Enum.reverse(seq) == [-2, 1, -1, 3] do - dbg({"total total", total}) - end - - if total >= max do - dbg({"new winner", total, seq}) - {total, seq} - else - {max, best_seq} - end + if res > acc do + dbg({"new high", res}) + res else - {max, best_seq} + acc end end) dbg(part2) end + defp score_seq(prices, diffs, seq) do + # dbg({"score seq", seq}) + + res = + Enum.zip(prices, diffs) + |> Enum.reduce(0, fn {p, d}, acc -> + case index_of_sublist(d, seq) do + nil -> acc + idx -> Enum.at(p, idx) + acc + end + end) + + res + # dbg(res) + end + defp evolve(secret) do step1 = secret |> Kernel.*(64) |> mix(secret) |> prune() step2 = step1 |> div(32) |> mix(step1) |> prune() @@ -112,18 +116,10 @@ defmodule Day22 do # finds the index of a sequence in a list, nil if it isn't in there defp index_of_sublist(parent, sublist) do - sub_len = length(sublist) - - Enum.reduce(0..(length(parent) - sub_len), nil, fn idx, acc -> - if acc do - acc - else - slice = Enum.slice(parent, idx, sub_len) - - if slice == sublist do - idx - end - end + Enum.reduce_while(0..(length(parent) - length(sublist)), nil, fn idx, acc -> + if Enum.slice(parent, idx, length(sublist)) == sublist, + do: {:halt, idx}, + else: {:cont, acc} end) end end From c489f6e77ae9d8b56398fe9ca652ddc8f40b6dde Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Thu, 26 Dec 2024 20:48:14 -0700 Subject: [PATCH 05/10] Day 22 again. FINALLY. Just needed a map --- day22/main.exs | 96 +++++++++++--------------------------------------- 1 file changed, 21 insertions(+), 75 deletions(-) diff --git a/day22/main.exs b/day22/main.exs index b111b4d..bfdee2d 100644 --- a/day22/main.exs +++ b/day22/main.exs @@ -13,89 +13,44 @@ defmodule Day22 do dbg(part1) - # lines = [123, 123] - - {prices, diffs} = - Enum.reduce(lines, {[], []}, fn secret, {prices_acc, diffs_acc} -> - {_, prices, diffs} = - Enum.reduce(1..2000, {secret, [price(secret)], []}, fn _, {s, prices, diffs} -> + # build a map of all 4-digit sequences + list index to prices + map = + Enum.with_index(lines) + |> Enum.reduce(%{}, fn {secret, idx}, map -> + {_, prices, diffs, map2} = + Enum.reduce(1..2000, {secret, [price(secret)], [], map}, fn _, {s, prices, diffs, m} -> new_secret = evolve(s) price = price(new_secret) diff = price - hd(prices) - {new_secret, [price | prices], [diff | diffs]} + new_diff = [diff | diffs] + sec = Enum.slice(new_diff, 0, 4) + new_map = Map.put_new(m, {sec, idx}, price) + {new_secret, [price | prices], new_diff, new_map} end) - {[prices | prices_acc], [diffs | diffs_acc]} + map2 end) - dbg({prices, diffs}) + # for every possible sequence (there's only like 19 ^ 4 or something) + all = for a <- -9..9, b <- -9..9, c <- -9..9, d <- -9..9, do: [a, b, c, d] - # max length of all price lists - max = Enum.map(prices, &length/1) |> Enum.max() - - # sort the lists by price, keeping their original index - sorted_by_price_with_index = - Enum.map(prices, fn prices -> - prices |> Enum.with_index() |> Enum.sort(fn {p1, _}, {p2, _} -> p1 >= p2 end) - end) - |> Enum.with_index() - - # dbg(sorted_by_price_with_index) - - # go from 0 up to the max list length of the lists sorted by price, highest to lowest - # grab the 4-sequence of each list - # check each sequence against each list, add up the prices - # if that price is greater than the max acc price, that's the new winning sequence part2 = - Enum.reduce(0..(max - 1), 0, fn index, acc -> - dbg({"checking all lists at index", index}) - dbg({"current acc", acc}) - - res = - Enum.reduce(sorted_by_price_with_index, {0, []}, fn {price_list, list_index}, - {max_price, max_seq} -> - {price, original_index} = Enum.at(price_list, index) - # dbg({diffs, index}) - seq = Enum.at(diffs, list_index) |> Enum.slice(original_index, 4) - # dbg({price, seq}) - - if length(seq) == 4 do - max_price = score_seq(prices, diffs, seq) - # dbg({"max_price", max_price}) - - {max_price, seq} - else - {max_price, max_seq} + Enum.reduce(all, {0, []}, fn sec, {max, max_sec} -> + # and for every buyer, get the price at this sequence + val = + Enum.reduce(0..length(lines), 0, fn idx, acc -> + case Map.get(map, {sec, idx}, nil) do + nil -> acc + val -> val + acc end end) - if res > acc do - dbg({"new high", res}) - res - else - acc - end + if val > max, do: {val, sec}, else: {max, max_sec} end) dbg(part2) end - defp score_seq(prices, diffs, seq) do - # dbg({"score seq", seq}) - - res = - Enum.zip(prices, diffs) - |> Enum.reduce(0, fn {p, d}, acc -> - case index_of_sublist(d, seq) do - nil -> acc - idx -> Enum.at(p, idx) + acc - end - end) - - res - # dbg(res) - end - defp evolve(secret) do step1 = secret |> Kernel.*(64) |> mix(secret) |> prune() step2 = step1 |> div(32) |> mix(step1) |> prune() @@ -113,15 +68,6 @@ defmodule Day22 do defp price(num) do Integer.digits(num) |> Enum.take(-1) |> Integer.undigits() end - - # finds the index of a sequence in a list, nil if it isn't in there - defp index_of_sublist(parent, sublist) do - Enum.reduce_while(0..(length(parent) - length(sublist)), nil, fn idx, acc -> - if Enum.slice(parent, idx, length(sublist)) == sublist, - do: {:halt, idx}, - else: {:cont, acc} - end) - end end Day22.run() From 2cd1da393224dddbe7a7a7d1f1ef2feabab4f6e3 Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Sat, 28 Dec 2024 23:37:06 -0700 Subject: [PATCH 06/10] Day 23. Had to look up some algorithm help. Runs pretty slowly, but I\'m happy with it --- day23/main.exs | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 day23/main.exs diff --git a/day23/main.exs b/day23/main.exs new file mode 100644 index 0000000..fa6a371 --- /dev/null +++ b/day23/main.exs @@ -0,0 +1,81 @@ +defmodule Day23 do + def run do + connections = + File.read!("data.txt") + |> String.trim() + |> String.split("\n") + |> Enum.map(fn el -> String.split(el, "-") end) + + all_computers = connections |> Enum.concat() |> Enum.uniq() + + # build a map of computers -> all their connected computers + map = + Enum.reduce(all_computers, %{}, fn el1, map -> + friends = + Enum.filter(all_computers, fn el2 -> + Enum.member?(connections, [el1, el2]) || Enum.member?(connections, [el2, el1]) + end) + + Map.put(map, el1, friends) + end) + + part1 = + expand(map, connections) + |> Enum.filter(fn set -> Enum.any?(set, fn el -> String.starts_with?(el, "t") end) end) + |> length + + dbg(part1) + + part2 = + loop_expand(map, connections) + |> hd() + |> Enum.join(",") + + dbg(part2) + end + + defp loop_expand(map, sets) do + case expand(map, sets) do + [] -> sets + x -> loop_expand(map, x) + end + end + + defp expand(map, sets) do + # look at an element in every set (e.g. the first element) + # check all of its connections + # if any of those connected computers are connected to all of the set members, you've increased the set size by 1! + Enum.reduce(sets, [], fn set, acc -> + connections = Map.get(map, hd(set), []) + + res = + Enum.reduce(connections, [], fn con, acc3 -> + # if it is connected to each member of the set, we've enlarged the set + if Enum.all?(set, fn set_el -> is_connected(map, set_el, con) end) do + [[con | set] | acc3] + else + acc3 + end + end) + + if Enum.empty?(res) do + acc + else + if is_list(Enum.at(res, 0)) do + Enum.concat(res, acc) + else + [res | acc] + end + end + end) + |> Enum.map(fn l -> Enum.sort(l) end) + |> Enum.uniq() + |> Enum.sort_by(fn a -> Enum.join(a) end) + end + + defp is_connected(map, a, b) do + Map.get(map, a, []) |> Enum.member?(b) + end +end + +Day23.run() From 791d6d9c699ed0947bed9c9390e5953cd5c59834 Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Sun, 29 Dec 2024 17:50:28 -0700 Subject: [PATCH 07/10] Day 24. Got part 1 pretty easily, but now I\'m not sure I should be using maps for the gates. Checking in now so I can refactor into just lists --- day24/main.exs | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 day24/main.exs diff --git a/day24/main.exs b/day24/main.exs new file mode 100644 index 0000000..7aa3bbc --- /dev/null +++ b/day24/main.exs @@ -0,0 +1,91 @@ +defmodule Day24 do + def run do + {wires, gates} = parse_input("test.txt") + + # part1 + part1 = run_loop(wires, gates) |> wire_value("z") |> b_to_d() + dbg(part1) + + # part2 + x = wire_value(wires, "x") |> b_to_d() + y = wire_value(wires, "y") |> b_to_d() + target = (x + y) |> d_to_b() + dbg(target) + dbg(b_to_d(target)) + end + + defp b_to_d(bin) do + :erlang.binary_to_integer(Integer.to_string(bin), 2) + end + + defp d_to_b(dec) do + Integer.to_string(dec, 2) |> String.to_integer() + end + + defp wire_value(wires, char) do + wires + |> Enum.filter(fn {k, _} -> String.starts_with?(k, char) end) + |> Enum.sort() + |> Enum.reduce([], fn {_, v}, acc -> [v | acc] end) + |> Integer.undigits() + end + + defp run_loop(wires, gates) do + new_wires = run(wires, gates) + + if map_size(new_wires) == map_size(wires) do + wires + else + run_loop(new_wires, gates) + end + end + + defp run(wires, gates) do + Enum.reduce(gates, wires, fn {{left, right}, effects}, acc -> + l = Map.get(wires, left, nil) + r = Map.get(wires, right, nil) + + if !is_nil(l) and !is_nil(r) do + Enum.reduce(effects, acc, fn {op, output}, acc2 -> + res = op.(l, r) + Map.put(acc2, output, res) + end) + else + acc + end + end) + end + + defp parse_input(file) do + [wires, gates] = + File.read!(file) + |> String.trim() + |> String.split("\n\n") + |> Enum.map(&String.split(&1, "\n")) + + wires = + Enum.reduce(wires, %{}, fn el, map -> + [wire, value] = String.split(el, ": ") + Map.put(map, wire, String.to_integer(value)) + end) + + gates = + Enum.reduce(gates, %{}, fn el, map -> + [logic, output] = String.split(el, " -> ") + [left, op, right] = String.split(logic, " ") + + op = + case op do + "AND" -> &Bitwise.band/2 + "OR" -> &Bitwise.bor/2 + "XOR" -> &Bitwise.bxor/2 + end + + Map.update(map, {left, right}, [{op, output}], fn acc -> [{op, output} | acc] end) + end) + + {wires, gates} + end +end + +Day24.run() From cadb944182439e478e87bc49a05aa3968c8fe851 Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Sun, 29 Dec 2024 17:51:09 -0700 Subject: [PATCH 08/10] Checking in Day 17 even though I haven't figured out part 2 yet. I\'m coming back to it --- day17/main.exs | 180 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 day17/main.exs diff --git a/day17/main.exs b/day17/main.exs new file mode 100644 index 0000000..07c9bc4 --- /dev/null +++ b/day17/main.exs @@ -0,0 +1,180 @@ +defmodule Day17 do + def run do + [registers, program] = + File.read!("data.txt") + |> String.trim() + |> String.split("\n\n") + |> Enum.with_index() + |> Enum.map(fn {lines, idx} -> + if idx == 0, do: parseRegisters(lines), else: parseProgram(lines) + end) + + # registers = %{A: 2024, B: 0, C: 0} + # program = [0, 1, 5, 4, 3, 0] + + initial_state = %{ + program: program, + registers: registers, + pointer: 0, + output: [] + } + + dbg(initial_state) + + state = run(initial_state) + output = Enum.reverse(state.output) + dbg(output) + + # part 2 + # target is [2, 4, 1, 6, 7, 5, 4, 4, 1, 7, 0, 3, 5, 5, 3, 0] + # noticing every power of 8 it adds a digit + # the new big digit goes [1], 0, 3, 2, 4, 4, 1, 5 + # when there's 1 digit, the biggest digit changes every 1 + # when there are 2 digits, the biggest digit changes every 8 + # when there are 3 digits, the biggest digit changes every 8 * 8 + # etc. + # when there are 16 digits, the biggest digit changes every 8 ^ 15 + # if the first digit is 2, that's the 3rd in the sequence, so 3x that + # + # 1 0 3 2 4 4 1 5 + # when 2 digits, the smaller digit goes: + # 0: 1 0 3 2 6 5 1 1 + # 3: 1 0 2 2 0 6 1 5 + # 2: 1 0 2 2 2 7 1 1 + # 4: 1 0 1 3 4 0 1 5 + # 4: 1 0 1 3 6 1 1 1 + # 1: 1 0 0 3 0 2 1 5 + # 5: 1 0 0 3 2 3 1 1 + # + # when 3 digits, smallest digit goes + # 0 0 7 0 4 4 1 5 + # + # starter = 3 * 8 ** 15 + 6 * 8 ** 14 + 6 * 8 ** 13 # interesting, all ones + starter = 2 * 8 ** 15 + increment = 8 ** 10 + + try(starter, initial_state, increment) + end + + defp try(a, state, increment) do + # IO.gets("") + new_state = %{state | registers: %{state.registers | A: a}} + val = run(new_state) + + # if Integer.mod(a, 1000) == 0 do + IO.puts(a) + dbg(a) + dbg(val) + # end + + if Enum.reverse(val.output) == state.program do + a + else + try(a + increment, state, increment) + end + end + + defp run( + %{ + program: program, + registers: registers, + pointer: pointer, + output: output + } = state + ) do + opcode = Enum.at(program, pointer) + operand = Enum.at(program, pointer + 1) + # dbg({"run", state}) + + if is_nil(opcode) do + state + else + new_state = + case opcode do + # adv + 0 -> + num = registers[:A] + den = Integer.pow(2, getComboOperandValue(operand, registers)) + new_regs = %{registers | A: trunc(num / den)} + + %{state | registers: new_regs, pointer: pointer + 2} + + # bxl + 1 -> + new_regs = %{registers | B: Bitwise.bxor(registers[:B], operand)} + %{state | registers: new_regs, pointer: pointer + 2} + + # bst + 2 -> + new_regs = %{ + registers + | B: Integer.mod(getComboOperandValue(operand, registers), 8) + } + + %{state | registers: new_regs, pointer: pointer + 2} + + # jnz + 3 -> + if registers[:A] == 0 do + %{state | pointer: pointer + 2} + else + %{state | pointer: operand} + end + + # bxc + 4 -> + new_regs = %{registers | B: Bitwise.bxor(registers[:B], registers[:C])} + %{state | registers: new_regs, pointer: pointer + 2} + + # out + 5 -> + val = Integer.mod(getComboOperandValue(operand, registers), 8) + %{state | pointer: pointer + 2, output: [val | output]} + + # bdv + 6 -> + num = registers[:A] + den = Integer.pow(2, getComboOperandValue(operand, registers)) + new_regs = %{registers | B: trunc(num / den)} + + %{state | registers: new_regs, pointer: pointer + 2} + + # cdv + 7 -> + num = registers[:A] + den = Integer.pow(2, getComboOperandValue(operand, registers)) + new_regs = %{registers | C: trunc(num / den)} + + %{state | registers: new_regs, pointer: pointer + 2} + end + + run(new_state) + end + end + + defp parseProgram(line) do + [_header, digits] = String.split(line, " ") + digits |> String.split(",") |> Enum.map(&String.to_integer/1) + end + + defp getComboOperandValue(op, registers) do + case op do + o when o <= 3 -> op + 4 -> registers[:A] + 5 -> registers[:B] + 6 -> registers[:C] + end + end + + defp parseRegisters(lines) do + lines + |> String.split("\n") + |> Enum.map(&String.split(&1, " ")) + |> Enum.reduce(%{}, fn [_header, label, value], acc -> + atom = String.trim(label, ":") |> String.to_atom() + Map.put(acc, atom, String.to_integer(value)) + end) + end +end + +Day17.run() From 65a54ef8b85e0168780cf94cbe51e8362bd28c0b Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Sun, 29 Dec 2024 18:04:13 -0700 Subject: [PATCH 09/10] Switched from maps to lists for the gates --- day24/main.exs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/day24/main.exs b/day24/main.exs index 7aa3bbc..7a4b6f1 100644 --- a/day24/main.exs +++ b/day24/main.exs @@ -1,6 +1,6 @@ defmodule Day24 do def run do - {wires, gates} = parse_input("test.txt") + {wires, gates} = parse_input("data.txt") # part1 part1 = run_loop(wires, gates) |> wire_value("z") |> b_to_d() @@ -41,15 +41,12 @@ defmodule Day24 do end defp run(wires, gates) do - Enum.reduce(gates, wires, fn {{left, right}, effects}, acc -> + Enum.reduce(gates, wires, fn %{left: left, right: right, output: output, op: op}, acc -> l = Map.get(wires, left, nil) r = Map.get(wires, right, nil) if !is_nil(l) and !is_nil(r) do - Enum.reduce(effects, acc, fn {op, output}, acc2 -> - res = op.(l, r) - Map.put(acc2, output, res) - end) + Map.put(acc, output, op.(l, r)) else acc end @@ -70,7 +67,7 @@ defmodule Day24 do end) gates = - Enum.reduce(gates, %{}, fn el, map -> + Enum.reduce(gates, [], fn el, map -> [logic, output] = String.split(el, " -> ") [left, op, right] = String.split(logic, " ") @@ -81,7 +78,7 @@ defmodule Day24 do "XOR" -> &Bitwise.bxor/2 end - Map.update(map, {left, right}, [{op, output}], fn acc -> [{op, output} | acc] end) + [%{left: left, right: right, op: op, output: output} | map] end) {wires, gates} From 689bf15df4b649064b39af8dfc515526b89fbc42 Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Wed, 1 Jan 2025 16:45:15 -0700 Subject: [PATCH 10/10] Day 25 part1 done Looks like I need to get all the other stars before I can do part 2? Never got this far! --- day25/main.exs | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 day25/main.exs diff --git a/day25/main.exs b/day25/main.exs new file mode 100644 index 0000000..e56f4b1 --- /dev/null +++ b/day25/main.exs @@ -0,0 +1,51 @@ +defmodule Day25 do + def run do + %{locks: locks, keys: keys} = parse_input("data.txt") + + # compare every key against every lock + good_keys = + Enum.reduce(keys, 0, fn key, acc -> + acc + + Enum.reduce(locks, 0, fn lock, acc2 -> + acc2 + if fits(lock, key), do: 1, else: 0 + end) + end) + + dbg(good_keys) + end + + defp fits(lock, key) do + Enum.reduce(0..4, true, fn idx, acc -> + acc and Enum.at(lock, idx) + Enum.at(key, idx) <= 5 + end) + end + + defp parse_input(file) do + File.read!(file) + |> String.trim() + |> String.split("\n\n") + |> Enum.reduce(%{locks: [], keys: []}, fn el, %{locks: locks, keys: keys} = acc -> + grid = String.split(el, "\n") |> Enum.map(&String.graphemes/1) + is_lock = grid |> hd() |> hd() |> String.graphemes() |> Enum.all?(fn el -> el == "#" end) + + res = + Enum.map(0..4, fn col -> + Enum.reduce(0..6, 0, fn row, acc2 -> + el = Enum.at(grid, row) |> Enum.at(col) + + case el do + "#" -> acc2 + 1 + _ -> acc2 + end + end) - 1 + end) + + case is_lock do + true -> %{acc | locks: [res | locks]} + false -> %{acc | keys: [res | keys]} + end + end) + end +end + +Day25.run()