diff --git a/2025/10/prolog/example.pl b/2025/10/prolog/example.pl new file mode 100644 index 0000000..dbbfbe3 --- /dev/null +++ b/2025/10/prolog/example.pl @@ -0,0 +1,3 @@ +input([0,1,1,0], [[3], [1,3], [2], [2,3], [0,2], [0,1]], [3,5,4,7]). +input([0,0,0,1,0], [[0,2,3,4], [2,3], [0,4], [0,1,2], [1,2,3,4]], [7,5,12,7,2]). +input([0,1,1,1,0,1], [[0,1,2,3,4], [0,3,4], [0,1,2,4,5], [1,2]], [10,11,11,5,10,5]). diff --git a/2025/10/prolog/main.pl b/2025/10/prolog/main.pl new file mode 100644 index 0000000..979a02f --- /dev/null +++ b/2025/10/prolog/main.pl @@ -0,0 +1,46 @@ +% [.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7} +% [...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2} +% [.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5} +% -> 2 + 3 + 2 = 7 + +:- consult('example.pl'). + +% Keep your toggle functions +toggle(Ls, [], Ls). +toggle(Ls, [I|Is], Out) :- + nth0(I, Ls, V), flip(V, F), set_nth0(I, Ls, F, T), toggle(T, Is, Out). +flip(0, 1). flip(1, 0). +set_nth0(0, [_|Rs], V, [V|Rs]). +set_nth0(N, [R|Rs], V, [R|Ts]) :- N > 0, N1 is N-1, set_nth0(N1, Rs, V, Ts). + +% Simple iterative deepening - no visited states needed +solve(L, _, [], 0) :- maplist(=(1), L). +solve(L, Wires, [W|Rest], D) :- + D > 0, member(W, Wires), + toggle(L, W, L1), + D1 is D - 1, + solve(L1, Wires, Rest, D1). + +% Find minimal solution +min_solve(L, Wires, Steps) :- + between(0, 6, Depth), % Limit depth to 6 + length(Steps, Depth), + solve(L, Wires, Steps, Depth). + + +solve_all :- + findall([L,W], input(L,W,_), Inputs), + maplist(solve_one, Inputs, Lengths), + sum_list(Lengths, Total), + format('Total: ~w~n', [Total]). + +solve_one([L,W], MinLen) :- + find_min_depth(L, W, MinDepth), + MinLen = MinDepth. + +find_min_depth(L, Wires, MinDepth) :- + between(0, 6, Depth), + length(Steps, Depth), + solve(L, Wires, Steps, Depth), !, + MinDepth = Depth. + diff --git a/2025/11/uiua/alt.ua b/2025/11/uiua/alt.ua new file mode 100644 index 0000000..b8e8469 --- /dev/null +++ b/2025/11/uiua/alt.ua @@ -0,0 +1,40 @@ +# day 11 + +Parse ← ( + ⊜(∩□⊙(⊜∘⊸≠@ )°$"_: _")⊸≠@\n + ∧(insert°□°⊟)⊙[]⍉⊟ +) +Go‼ ← ⊙◌path(0°□⍣get|≍^1)^0 +Part₁ ← ⧻Go‼"you" "out" +# Part₂ ← ⧻⊚≡◇(↧∩/↥⌕□"fft"⤙⌕□"dac"≡□)Go‼"svr" "out" +Win‼ ← ( + ⊃Go‼^0 "out" Go‼"svr" ^0 + +⊃(×⊙/+⧻|×⊙⧻/+)∩≡◇(/↥≡/↥⌕^1) +) +Part₂ ← ↥⊃Win‼"fft" "dac" Win‼"dac" "fft" +⍤⤙≍ 5 Part₁ Parse $ aaa: you hhh + $ you: bbb ccc + $ bbb: ddd eee + $ ccc: ddd eee fff + $ ddd: ggg + $ eee: out + $ fff: out + $ ggg: out + $ hhh: ccc fff iii + $ iii: out + +⍤⤙≍ 2 Part₂ Parse $ svr: aaa bbb + $ aaa: fft + $ fft: ccc + $ bbb: tty + $ tty: ccc + $ ccc: ddd eee + $ ddd: hub + $ hub: fff + $ eee: dac + $ dac: fff + $ fff: ggg hhh + $ ggg: out + $ hhh: out + +⍜now(Part₂ Parse &fras "input") diff --git a/2025/11/uiua/main.ua b/2025/11/uiua/main.ua new file mode 100644 index 0000000..3091847 --- /dev/null +++ b/2025/11/uiua/main.ua @@ -0,0 +1,36 @@ +# day 11 + +Parse ← ( + ⊜(∩□⊙(⊜∘⊸≠@ )°$"_: _")⊸≠@\n + ∧(insert°□°⊟)⊙[]⍉⊟ +) +Go‼ ← ⊙◌path(0°□memoget|≍^1)^0 +Part₁ ← ⧻Go‼"you" "out" +Part₂ ← ⧻⊚≡◇(↧∩/↥⌕□"fft"⤙⌕□"dac"≡□)Go‼"svr" "out" + +⍤⤙≍ 5 Part₁ Parse $ aaa: you hhh + $ you: bbb ccc + $ bbb: ddd eee + $ ccc: ddd eee fff + $ ddd: ggg + $ eee: out + $ fff: out + $ ggg: out + $ hhh: ccc fff iii + $ iii: out + +⍤⤙≍ 2 Part₂ Parse $ svr: aaa bbb + $ aaa: fft + $ fft: ccc + $ bbb: tty + $ tty: ccc + $ ccc: ddd eee + $ ddd: hub + $ hub: fff + $ eee: dac + $ dac: fff + $ fff: ggg hhh + $ ggg: out + $ hhh: out + +⍜now(Part₂ Parse &fras "input") diff --git a/2025/2/odin/main b/2025/2/odin/main new file mode 100755 index 0000000..ab6a2cd Binary files /dev/null and b/2025/2/odin/main differ diff --git a/2025/2/odin/main.odin b/2025/2/odin/main.odin new file mode 100644 index 0000000..23fac39 --- /dev/null +++ b/2025/2/odin/main.odin @@ -0,0 +1,76 @@ +package day2 + +import "core:fmt" +import "core:math" +import "core:strconv" +import "core:strings" + +EXAMPLE_INPUT :: "11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124" +RANGE_COUNT :: 11 + +Data :: [RANGE_COUNT][2]int + +// split input string into an array of each range end point +parse :: proc(input: string, allocator := context.allocator) -> (data: ^Data, ok: bool) { + defer free_all(context.temp_allocator) + data = new(Data, allocator) + ranges, err := strings.split_n(input, ",", RANGE_COUNT, context.temp_allocator) + if err != .None do return nil, false + for range, i in ranges { + if i > RANGE_COUNT do break + endpoints, err := strings.split_n(range, "-", 2, context.temp_allocator) + if err != .None do return nil, false + for j := 0; j < 2; j += 1 do data[i][j] = strconv.parse_int(endpoints[j]) or_return + } + return data, true +} + +part_1 :: proc(data: ^Data) -> (sum: int) { + defer free_all(context.temp_allocator) + sum = 0 + for range in data { + start := range[0] + end := range[1] + start_digit_count := int(math.log10(f32(start))) + 1 + end_digit_count := int(math.log10(f32(end))) + 1 + if start_digit_count == end_digit_count && start_digit_count % 2 != 0 do continue + if start_digit_count % 2 != 0 { + start = end + start_digit_count = end_digit_count + } + if end_digit_count % 2 != 0 { + end = start + end_digit_count = start_digit_count + } + for j := strconv.atoi(string(start)[:start_digit_count / 2 + 1]); + j < strconv.atoi(string(end)[:end_digit_count / 2 + 1]); + j += 1 {} + start_str := make([]u8, start_digit_count, context.temp_allocator) + strconv.itoa(start_str, start) + for i := 0; i < start_digit_count; i += 1 { + if i >= start_digit_count / 2 { + start_str[i] = start_str[i % (start_digit_count / 2)] + } + } + end_str := make([]u8, end_digit_count, context.temp_allocator) + strconv.itoa(end_str, end) + for i := 0; i < end_digit_count; i += 1 { + if i >= end_digit_count / 2 { + end_str[i] = end_str[i % (end_digit_count / 2)] + } + } + fmt.println(start, end, strconv.atoi(string(start_str)), strconv.atoi(string(end_str))) + } + return +} + +main :: proc() { + data, ok := parse(EXAMPLE_INPUT) + if !ok do panic("failed to parse") + defer free(data) + + part_1(data) + + fmt.println(data) + +} diff --git a/2025/3/odin/main.odin b/2025/3/odin/main.odin new file mode 100644 index 0000000..c4fcd8f --- /dev/null +++ b/2025/3/odin/main.odin @@ -0,0 +1,141 @@ +package day3 + +import "core:fmt" +import "core:mem" +import "core:os" +import "core:strings" +import "core:time" + +PUZZLE_INPUT :: ` + 987654321111111 + 811111111111119 + 234234234234278 + 818181911112111 +` + +Data_Is_Empty_Error :: distinct struct{} + +Data_Is_Ragged_Error :: distinct struct { + line: int, +} + +Illegal_Token_Error :: distinct struct { + line, col: int, + token: rune, +} + +Parsing_Error :: union { + mem.Allocator_Error, + Illegal_Token_Error, + Data_Is_Ragged_Error, + Data_Is_Empty_Error, +} + +Grid :: struct { + data: []int, + rows, cols: int, +} + +parse :: proc(input: string, allocator := context.allocator) -> (grid: Grid, err: Parsing_Error) { + context.allocator = context.temp_allocator + defer context.allocator = allocator + + lines := strings.split_lines(strings.trim_space(input)) or_return + if lines[0] == "" do return {}, Data_Is_Empty_Error{} + + rows := len(lines) + cols := len(strings.trim_space(lines[0])) + + data := make([dynamic]int, 0, rows * cols, allocator) or_return + + for line, row in lines { + clean_line := strings.trim_space(line) + if len(clean_line) != cols do return {}, Data_Is_Ragged_Error{row} + + for char, col in clean_line { + digit := int(char - '0') + if digit < 0 || digit > 9 do return {}, Illegal_Token_Error{row, col, char} + append(&data, digit) or_return + } + } + + return Grid{data[:], rows, cols}, nil +} + +remove_digits :: proc( + row: []int, + k: int, + allocator := context.allocator, +) -> ( + output: []int, + err: mem.Allocator_Error, +) { + stack := make([dynamic]int, 0, len(row) - k, allocator) or_return + dels := k + for digit in row { + for len(stack) > 0 && digit > stack[len(stack) - 1] && dels > 0 { + pop(&stack) + dels -= 1 + } + append(&stack, digit) or_return + } + return stack[:], .None +} + +process :: proc(row: []int, n: int) -> (output: int, err: mem.Allocator_Error) { + digits := remove_digits(row, len(row) - n) or_return + for digit in digits[:n] { + output = 10 * output + digit + } + return +} + +solve :: proc(grid: Grid, n: int) -> (solution: int, err: mem.Allocator_Error) { + context.allocator = context.temp_allocator + + bank_solutions := make([]int, grid.rows) or_return + + for row in 0 ..< grid.rows { + row_data := grid.data[row * grid.cols:(row + 1) * grid.cols] + bank_solutions[row] = process(row_data, n) or_return + } + + for bank in bank_solutions do solution += bank + return +} + +part_1 :: proc(grid: Grid) -> (solution: int, err: mem.Allocator_Error) { + return solve(grid, 2) +} + +part_2 :: proc(grid: Grid) -> (solution: int, err: mem.Allocator_Error) { + return solve(grid, 12) +} + +main :: proc() { + time_start := time.now() + input := string(os.read_entire_file("input") or_else panic("failed to read input")) + time_read := time.since(time_start) + + data, parsing_error := parse(input, context.temp_allocator) + defer free_all(context.temp_allocator) + if parsing_error != nil do fmt.panicf("failed to parse: %v\n", parsing_error) + + time_parse := time.since(time_start) - time_read + fmt.println("part 1: ", part_1(data) or_else panic("part 1 failed")) + time_solve_1 := time.since(time_start) - time_read - time_parse + fmt.println("part 2: ", part_2(data) or_else panic("part 2 failed")) + time_solve_2 := time.since(time_start) - time_read - time_parse - time_solve_1 + time_total := time.since(time_start) + + fmt.println("performance benchmarks [ms]:") + fmt.println("total\tread\tparse\tpart 1\tpart 2") + fmt.printf( + "%.3f\t%.3f\t%.3f\t%.3f\t%.3f\n", + time.duration_milliseconds(time_total), + time.duration_milliseconds(time_read), + time.duration_milliseconds(time_parse), + time.duration_milliseconds(time_solve_1), + time.duration_milliseconds(time_solve_2), + ) +} diff --git a/2025/5/uiua/main.ua b/2025/5/uiua/main.ua new file mode 100644 index 0000000..c9fa7c3 --- /dev/null +++ b/2025/5/uiua/main.ua @@ -0,0 +1,20 @@ +Parse ← ( + ∩(⊜□⊸≠@\n)°$"_\n\n_" + ⊃(⍉⊟≡◇(∩⋕°$"_-_")|⋅≡◇⋕) +) +Part₁ ← ⧻⊚/↥⊞(↧≤⤙⊙≤°⊟) +Part₂ ← ⧻◴/◇⊂⍚/⍜-(⇡+1) + +⍤⤙≍ 3_14 ⊃[Part₁|Part₂] Parse $ 3-5 + $ 10-14 + $ 16-20 + $ 12-18 + $ + $ 1 + $ 5 + $ 8 + $ 11 + $ 17 + $ 32 + +Part₂ Parse &fras "input.txt" diff --git a/2025/7/odin/example b/2025/7/odin/example new file mode 100644 index 0000000..57a2466 --- /dev/null +++ b/2025/7/odin/example @@ -0,0 +1,16 @@ +.......S....... +............... +.......^....... +............... +......^.^...... +............... +.....^.^.^..... +............... +....^.^...^.... +............... +...^.^...^.^... +............... +..^...^.....^.. +............... +.^.^.^.^.^...^. +............... diff --git a/2025/7/odin/main.odin b/2025/7/odin/main.odin new file mode 100644 index 0000000..4deaf8e --- /dev/null +++ b/2025/7/odin/main.odin @@ -0,0 +1,101 @@ +package day7 + +import "core:fmt" +import "core:slice" +import "core:strings" +import "core:time" + +PUZZLE_INPUT: string : #load("input") + +Pos :: distinct struct { + row: int, + col: int, +} + +Parsed_Data :: struct { + lines: []string, + rows, cols: int, + start: Pos, +} + +not_empty_line :: proc(line: string) -> (output: bool) { + output = true + for char in line do output &&= char == '.' + return !output +} + +parse :: proc(input: string) -> (output: Parsed_Data) { + lines := strings.split_lines(input) + output.start = {0, strings.index(lines[0], "S")} + output.lines = slice.filter(lines, not_empty_line) + output.rows = len(output.lines) + output.cols = len(output.lines[0]) + return +} + +solve_part_1 :: proc(data: Parsed_Data) -> (output: int) { + beams := make(map[Pos]bool, context.temp_allocator) + hit := make(map[Pos]bool, context.temp_allocator) + defer free_all(context.temp_allocator) + beams[data.start] = true + for line, row in data.lines { + for char, col in line { + for pos, exists in beams { + if exists && char == '^' && pos == {row - 1, col} { + beams[{row, col + 1}] = true + beams[{row, col - 1}] = true + hit[{row, col}] = true + } else if pos == {row - 1, col} { + beams[{row, col}] = true + } + } + } + } + return len(hit) +} + +solve_part_2 :: proc(data: Parsed_Data) -> (output: int) { + memo := make(map[Pos]int) + defer delete(memo) + + count_timelines :: proc(lines: []string, row, col: int, memo: ^map[Pos]int) -> int { + pos := Pos{row, col} + + // Check memo first + if cached, exists := memo[pos]; exists { + return cached + } + + // Bounds check + if row >= len(lines) || col < 0 || col >= len(lines[0]) { + memo[pos] = 1 + return 1 + } + + char := lines[row][col] + result: int + + if char == '^' { + left := count_timelines(lines, row + 1, col - 1, memo) + right := count_timelines(lines, row + 1, col + 1, memo) + result = left + right + } else { + result = count_timelines(lines, row + 1, col, memo) + } + + memo[pos] = result + return result + } + + return count_timelines(data.lines, data.start.row, data.start.col, &memo) +} + + +main :: proc() { + start := time.now() + data := parse(PUZZLE_INPUT) + fmt.println("part 1 solution:", solve_part_1(data)) + fmt.println("part 2 solution:", solve_part_2(data)) + total := time.since(start) + fmt.println("total time taken:", total) +} diff --git a/2025/8/debug_build b/2025/8/debug_build new file mode 100755 index 0000000..b62ec9f Binary files /dev/null and b/2025/8/debug_build differ diff --git a/2025/8/example b/2025/8/example new file mode 100644 index 0000000..20c6fd2 --- /dev/null +++ b/2025/8/example @@ -0,0 +1,20 @@ +162,817,812 +57,618,57 +906,360,560 +592,479,940 +352,342,300 +466,668,158 +542,29,236 +431,825,988 +739,650,466 +52,470,668 +216,146,977 +819,987,18 +117,168,530 +805,96,715 +346,949,466 +970,615,88 +941,993,340 +862,61,35 +984,92,344 +425,690,689 \ No newline at end of file diff --git a/2025/8/main.odin b/2025/8/main.odin new file mode 100644 index 0000000..5ea5ad9 --- /dev/null +++ b/2025/8/main.odin @@ -0,0 +1,50 @@ +package main + +import "core:fmt" +import "core:slice" +import "core:strconv" +import "core:strings" + +PUZZLE_INPUT: string : #load("example") + +Pos :: distinct struct { + x, y, z: int, +} + +parse :: proc(input: string) -> (output: []Pos) { + lines := strings.split_lines(input) + output = make([]Pos, len(lines)) + digits := make([dynamic]int, context.temp_allocator) + defer free_all(context.temp_allocator) + nums: [3]int + for line, row in lines { + for num, i in strings.split(line, ",") do nums[i], _ = strconv.parse_int(num) + output[row] = Pos{nums[0], nums[1], nums[2]} + } + return +} + +solve :: proc(data: []Pos) -> (output: int) { + context.allocator = context.temp_allocator + + distances := make([]int, len(data) * len(data)) + for p1, row in data { + for p2, col in data { + d := Pos{p1.x - p2.x, p1.y - p2.y, p1.z - p2.z} + distance := d.x * d.x + d.y * d.y + d.z * d.z + distances[row * len(data) + col] = distance == 0 ? 1e9 : distance // TODO: triangle + } + } + fmt.println(distances) + shortest := make([]int, len(data)) + for i in 0 ..< len(data) { + row := distances[i * len(data):(i + 1) * len(data)] + shortest[i] = slice.min_index(row) + } + fmt.println(shortest) + return +} + +main :: proc() { + fmt.println(solve(parse(PUZZLE_INPUT))) +}