summaryrefslogtreecommitdiff
path: root/aoc24/src/day7.zig
blob: 912eac1b3bf8a4c6d63ad3ccd8f27821bf54949a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
const std = @import("std");

const Equation = struct { total: u64, numbers: []u64 };

const Input = []Equation;

pub fn parse(allocator: std.mem.Allocator, data: []const u8) !Input {
    var lines = std.mem.split(u8, data, "\n");
    var equations = std.ArrayList(Equation).init(allocator);
    while (lines.next()) |line| {
        if (line.len == 0) continue;
        var split = std.mem.split(u8, line, " ");
        const total = try std.fmt.parseInt(
            u64,
            std.mem.trimRight(u8, split.next() orelse return error.InvalidInput, ":"),
            10,
        );
        var numbers = std.ArrayList(u64).init(allocator);
        while (split.next()) |number| {
            try numbers.append(try std.fmt.parseInt(u64, number, 10));
        }
        try equations.append(.{ .total = total, .numbers = numbers.items });
    }
    return equations.items;
}

const test_input =
    \\190: 10 19
    \\3267: 81 40 27
    \\83: 17 5
    \\156: 15 6
    \\7290: 6 8 6 15
    \\161011: 16 10 13
    \\192: 17 8 14
    \\21037: 9 7 18 13
    \\292: 11 6 16 20
    \\
;

test "part1" {
    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
    defer arena.deinit();

    const output = try part1(arena.allocator(), try parse(arena.allocator(), test_input));
    std.debug.print("got {}\n", .{output});
    std.debug.assert(output == 3749);
}

fn can_become(total: u64, numbers: []u64, target: u64) bool {
    // std.debug.print("can_become({}, {any}, {})\n", .{ total, numbers, target });
    if (numbers.len == 0) return total == target;
    const last = numbers.len - 1;
    return total >= numbers[last] and can_become(total - numbers[last], numbers[0..last], 0) or
        total % numbers[last] == 0 and can_become(total / numbers[last], numbers[0..last], 1);
}

pub fn part1(_: std.mem.Allocator, input: Input) !u64 {
    var sum: u64 = 0;
    for (input) |equation| {
        if (can_become(equation.total, equation.numbers, 0))
            sum += equation.total;
        // std.debug.print("\n", .{});
    }

    return sum;
}

test "part2" {
    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
    defer arena.deinit();

    const output = try part2(arena.allocator(), try parse(arena.allocator(), test_input));
    std.debug.print("got {}\n", .{output});
    std.debug.assert(output == 11387);
}

fn can_become2(total: u64, numbers: []u64, target: u64) bool {
    // std.debug.print("can_become({}, {any}, {})\n", .{ total, numbers, target });
    if (numbers.len == 0) return total == target;
    const last = numbers.len - 1;
    // Addition
    return (total >= numbers[last] and can_become2(total - numbers[last], numbers[0..last], 0)) or
        // Multiplication
        (total % numbers[last] == 0 and can_become2(total / numbers[last], numbers[0..last], 1)) or blk: {
        // Concatenation
        const digit_count = std.fmt.count("{}", .{numbers[last]});
        const concat_mult = std.math.pow(u64, 10, digit_count);
        break :blk (total >= numbers[last] and (total - numbers[last]) % concat_mult == 0 and can_become2((total - numbers[last]) / concat_mult, numbers[0..last], 0));
    };
}

pub fn part2(_: std.mem.Allocator, input: Input) !u64 {
    var sum: u64 = 0;
    for (input) |equation| {
        if (can_become2(equation.total, equation.numbers, 0))
            sum += equation.total;
        // std.debug.print("\n", .{});
    }

    return sum;
}