diff --git a/day17/main.exs b/day17/main.exs deleted file mode 100644 index 07c9bc4..0000000 --- a/day17/main.exs +++ /dev/null @@ -1,180 +0,0 @@ -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() diff --git a/day20/main.exs b/day20/main.exs deleted file mode 100644 index 3d99c3d..0000000 --- a/day20/main.exs +++ /dev/null @@ -1,112 +0,0 @@ -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() diff --git a/day21/main.exs b/day21/main.exs deleted file mode 100644 index 5044ae6..0000000 --- a/day21/main.exs +++ /dev/null @@ -1,181 +0,0 @@ -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() diff --git a/day22/main.exs b/day22/main.exs deleted file mode 100644 index bfdee2d..0000000 --- a/day22/main.exs +++ /dev/null @@ -1,73 +0,0 @@ -defmodule Day22 do - def run do - lines = - File.read!("data.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) - - # 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_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) - - map2 - end) - - # 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] - - part2 = - 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 val > max, do: {val, sec}, else: {max, max_sec} - 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 -end - -Day22.run() diff --git a/day23/main.exs b/day23/main.exs deleted file mode 100644 index fa6a371..0000000 --- a/day23/main.exs +++ /dev/null @@ -1,81 +0,0 @@ -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() diff --git a/day24/main.exs b/day24/main.exs deleted file mode 100644 index 7a4b6f1..0000000 --- a/day24/main.exs +++ /dev/null @@ -1,88 +0,0 @@ -defmodule Day24 do - def run do - {wires, gates} = parse_input("data.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: 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 - Map.put(acc, output, op.(l, r)) - 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 - - [%{left: left, right: right, op: op, output: output} | map] - end) - - {wires, gates} - end -end - -Day24.run() diff --git a/day25/main.exs b/day25/main.exs deleted file mode 100644 index e56f4b1..0000000 --- a/day25/main.exs +++ /dev/null @@ -1,51 +0,0 @@ -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()