aboutsummaryrefslogtreecommitdiffstats
path: root/src/18/main.zig
blob: 913a657f0e8734dfcaf16bfff4246f411597be7a (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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// Own twist on https://en.wikipedia.org/wiki/Shunting-yard_algorithm

const std = @import("std");
const aoc = @import("aoc");

const OpType = enum { ADD, MULT, COUNT };
const Op = struct { prio: u32, optype: OpType };
const Item = union(enum) { num: i64, op: OpType };

const op_names = [_][]const u8{ "ADD", "MULT" };

fn printStack(stack: []Item) void {
    for (stack) |item| {
        switch (item) {
            .op => aoc.debugfmt("{s} ", .{op_names[@enumToInt(item.op)]}),
            .num => aoc.debugfmt("{} ", .{item.num}),
        }
    }
    aoc.debugfmt("\n", .{});
}

fn parseToken(line: []const u8, i: *usize, depth: *u32, op_prio: []const u32, mathstack: *std.ArrayList(Item), opstack: *std.ArrayList(Op)) !bool {
    if (i.* > line.len) {
        return false;
    } else if (i.* == line.len) {
        while (opstack.items.len > 0)
            try mathstack.append(Item{ .op = opstack.pop().optype });
    } else {
        const c = line[i.*];
        switch (c) {
            '+', '*' => {
                const optype = switch (c) {
                    '+' => OpType.ADD,
                    '*' => OpType.MULT,
                    else => unreachable,
                };
                const prio = depth.* * @enumToInt(OpType.COUNT) + op_prio[@enumToInt(optype)];
                while (opstack.items.len > 0 and
                    opstack.items[opstack.items.len - 1].prio >= prio)
                {
                    try mathstack.append(Item{ .op = opstack.pop().optype });
                }
                try opstack.append(Op{ .optype = optype, .prio = prio });
            },
            '(' => depth.* += 1,
            ')' => depth.* -= 1,
            ' ' => {},
            else => {
                // in this case we know that every number is one digit..
                // crappy zig parseInt requires span of exact int size
                // before we even know the format >:(((
                const val = try std.fmt.parseInt(i64, line[i.* .. i.* + 1], 10);
                try mathstack.append(Item{ .num = val });
            },
        }
    }
    i.* += 1;
    return true;
}

fn reduceStack(mathstack: *std.ArrayList(Item), opstack: *std.ArrayList(Op), tmpstack: *std.ArrayList(Item)) !void {
    _ = opstack;

    while (mathstack.items.len > 0) {
        while (mathstack.items[mathstack.items.len - 1] != .op and
            tmpstack.items.len > 0)
        {
            try mathstack.append(tmpstack.pop());
        }
        if (mathstack.items.len < 3 or mathstack.items[mathstack.items.len - 1] != .op)
            break;
        const op = mathstack.pop();

        // get first arg
        if (mathstack.items[mathstack.items.len - 1] != .num) {
            try tmpstack.append(op);
            continue;
        }
        const a = mathstack.pop();

        // get second arg
        if (mathstack.items[mathstack.items.len - 1] != .num) {
            try tmpstack.append(op);
            try tmpstack.append(a);
            continue;
        }
        const b = mathstack.pop();

        const result: i64 = switch (op.op) {
            OpType.ADD => a.num + b.num,
            OpType.MULT => a.num * b.num,
            else => unreachable,
        };
        try mathstack.append(Item{ .num = result });
    }
}

fn printRPN(allocator: std.mem.Allocator, input: []u8, prios: []const u32) !void {
    var mathstack = std.ArrayList(Item).init(allocator);
    defer mathstack.deinit();

    var opstack = std.ArrayList(Op).init(allocator);
    defer opstack.deinit();

    var lineit = std.mem.tokenize(u8, input, "\n");
    while (lineit.next()) |line| {
        var depth: u32 = 0;
        var i: usize = 0;
        while (try parseToken(line, &i, &depth, prios, &mathstack, &opstack)) {}
    }

    printStack(mathstack.items);
}

fn partProto(allocator: std.mem.Allocator, input: []u8, prios: []const u32) !?[]u8 {
    var answer: i64 = 0;

    if (aoc.debug) {
        aoc.debugfmt("Reverse Polish Notation:\n", .{});
        try printRPN(allocator, input, prios);
    }

    var mathstack = std.ArrayList(Item).init(allocator);
    defer mathstack.deinit();

    var opstack = std.ArrayList(Op).init(allocator);
    defer opstack.deinit();

    var tmpstack = std.ArrayList(Item).init(allocator);
    defer tmpstack.deinit();

    var lineit = std.mem.tokenize(u8, input, "\n");
    while (lineit.next()) |line| {
        var depth: u32 = 0;
        var i: usize = 0;
        while (try parseToken(line, &i, &depth, prios, &mathstack, &opstack)) {
            try reduceStack(&mathstack, &opstack, &tmpstack);
        }

        if (mathstack.items.len != 1) return aoc.Error.InvalidInput;
        const last = mathstack.items[mathstack.items.len - 1];
        if (last != .num) return aoc.Error.InvalidInput;
        answer += last.num;

        try mathstack.resize(0);
        try opstack.resize(0);
        try tmpstack.resize(0);
    }

    return try std.fmt.allocPrint(allocator, "{}", .{answer});
}

fn part1(allocator: std.mem.Allocator, input: []u8, args: [][]u8) !?[]u8 {
    _ = args;

    const prios = [_]u32{ 1, 1 };

    return try partProto(allocator, input, prios[0..]);
}

fn part2(allocator: std.mem.Allocator, input: []u8, args: [][]u8) !?[]u8 {
    _ = args;

    const prios = [_]u32{ 2, 1 };

    return try partProto(allocator, input, prios[0..]);
}

test "unions" {
    const item1 = Item{ .op = OpType.MULT };
    const item2 = Item{ .num = 1000 };
    const expect = std.testing.expect;
    expect(item1 == .op);
    expect(item2 == .num);
}

pub const main = aoc.main(part1, part2, .{ "5783053349377", "74821486966872" });