From 58ea13ad30f4aab864b5684b5ded542ed89a004d Mon Sep 17 00:00:00 2001 From: Akuli Date: Sat, 4 Jan 2025 20:41:49 +0200 Subject: [PATCH 01/12] day24 part1 --- examples/aoc2024/day24/part1.jou | 140 +++++++++++++++++++++++++ examples/aoc2024/day24/sampleinput.txt | 47 +++++++++ 2 files changed, 187 insertions(+) create mode 100644 examples/aoc2024/day24/part1.jou create mode 100644 examples/aoc2024/day24/sampleinput.txt diff --git a/examples/aoc2024/day24/part1.jou b/examples/aoc2024/day24/part1.jou new file mode 100644 index 00000000..7878d05e --- /dev/null +++ b/examples/aoc2024/day24/part1.jou @@ -0,0 +1,140 @@ +import "stdlib/str.jou" +import "stdlib/ascii.jou" +import "stdlib/io.jou" + + +enum LogicOp: + And + Or + Xor + + +class LogicGate: + inputs: int*[2] # -1 = undefined, otherwise 0 or 1 + output: int* + op: LogicOp + + def run(self) -> None: + if *self->inputs[0] == -1 or *self->inputs[1] == -1: + return + + i1 = (*self->inputs[0] == 1) + i2 = (*self->inputs[1] == 1) + + if self->op == LogicOp::And: + *self->output = (i1 and i2) as int + elif self->op == LogicOp::Or: + *self->output = (i1 or i2) as int + elif self->op == LogicOp::Xor: + *self->output = (i1 != i2) as int + else: + assert False + + +class VariableManager: + variables: int[500] + nvariables: int + varnames: byte[4][500] + + def get_var(self, name: byte*) -> int*: + for i = 0; i < self->nvariables; i++: + if strcmp(name, self->varnames[i]) == 0: + return &self->variables[i] + return NULL + + def get_or_create_var(self, name: byte*) -> int*: + exist = self->get_var(name) + if exist != NULL: + return exist + + assert self->nvariables < 500 + i = self->nvariables++ + + assert strlen(name) == 3 + strcpy(self->varnames[i], name) + self->variables[i] = -1 # uninitialized + + return &self->variables[i] + + def get_result(self) -> long: + # Variables starting with z are named z00, z01, z02, ... + # Loop backwards to get bit order right. + zcount = 0 + for i = 0; i < self->nvariables; i++: + if self->varnames[i][0] == 'z': + zcount++ + + result = 0L + for i = zcount-1; i >= 0; i--: + name: byte[100] + sprintf(name, "z%02d", i) + + v = self->get_var(name) + assert v != NULL + if *v == -1: + return -1 + + result *= 2 + result += *v + + return result + + +def main() -> int: + varmgr = VariableManager{} + + gates: LogicGate[500] + ngates = 0 + + f = fopen("input", "r") + assert f != NULL + + line: byte[100] + while fgets(line, sizeof(line) as int, f) != NULL: + trim_ascii_whitespace(line) + if line[0] == '\0': + # end of initial values, start of logic gates + break + + # initial value of variable, e.g. "x01: 1" + assert strlen(line) == 6 + assert line[3] == ':' + line[3] = '\0' + *varmgr.get_or_create_var(line) = atoi(&line[5]) + + input1, op, input2, output: byte[4] + while fscanf(f, "%3s %3s %3s -> %3s\n", input1, op, input2, output) == 4: + if strcmp(op, "AND") == 0: + op_enum = LogicOp::And + elif strcmp(op, "OR") == 0: + op_enum = LogicOp::Or + elif strcmp(op, "XOR") == 0: + op_enum = LogicOp::Xor + else: + assert False + + assert ngates < sizeof(gates)/sizeof(gates[0]) + gates[ngates++] = LogicGate{ + inputs = [varmgr.get_or_create_var(input1), varmgr.get_or_create_var(input2)], + output = varmgr.get_or_create_var(output), + op = op_enum, + } + + # Check that no gate has multiple outputs hooked up to its input + for i = 0; i < ngates; i++: + for k = 0; k < 2; k++: + var = gates[i].inputs[k] + count = 0 + for g = &gates[0]; g < &gates[ngates]; g++: + if g->output == var: + count++ + assert count <= 1 + + while varmgr.get_result() == -1: + for g = &gates[0]; g < &gates[ngates]; g++: + g->run() + + printf("%lld\n", varmgr.get_result()) # Output: 2024 + + fclose(f) + return 0 diff --git a/examples/aoc2024/day24/sampleinput.txt b/examples/aoc2024/day24/sampleinput.txt new file mode 100644 index 00000000..94b6eed6 --- /dev/null +++ b/examples/aoc2024/day24/sampleinput.txt @@ -0,0 +1,47 @@ +x00: 1 +x01: 0 +x02: 1 +x03: 1 +x04: 0 +y00: 1 +y01: 1 +y02: 1 +y03: 1 +y04: 1 + +ntg XOR fgs -> mjb +y02 OR x01 -> tnw +kwq OR kpj -> z05 +x00 OR x03 -> fst +tgd XOR rvg -> z01 +vdt OR tnw -> bfw +bfw AND frj -> z10 +ffh OR nrd -> bqk +y00 AND y03 -> djm +y03 OR y00 -> psh +bqk OR frj -> z08 +tnw OR fst -> frj +gnj AND tgd -> z11 +bfw XOR mjb -> z00 +x03 OR x00 -> vdt +gnj AND wpb -> z02 +x04 AND y00 -> kjc +djm OR pbm -> qhw +nrd AND vdt -> hwm +kjc AND fst -> rvg +y04 OR y02 -> fgs +y01 AND x02 -> pbm +ntg OR kjc -> kwq +psh XOR fgs -> tgd +qhw XOR tgd -> z09 +pbm OR djm -> kpj +x03 XOR y03 -> ffh +x00 XOR y04 -> ntg +bfw OR bqk -> z06 +nrd XOR fgs -> wpb +frj XOR qhw -> z04 +bqk OR frj -> z07 +y03 OR x01 -> nrd +hwm AND bqk -> z03 +tgd XOR rvg -> z12 +tnw OR pbm -> gnj From 53c4cf27a797e0dd605818b125cc02215f7065d5 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sat, 4 Jan 2025 23:31:21 +0200 Subject: [PATCH 02/12] wip --- examples/aoc2024/day24/.gitignore | 2 + examples/aoc2024/day24/notes.txt | 23 +++ examples/aoc2024/day24/part2.jou | 251 ++++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 examples/aoc2024/day24/.gitignore create mode 100644 examples/aoc2024/day24/notes.txt create mode 100644 examples/aoc2024/day24/part2.jou diff --git a/examples/aoc2024/day24/.gitignore b/examples/aoc2024/day24/.gitignore new file mode 100644 index 00000000..8766ee97 --- /dev/null +++ b/examples/aoc2024/day24/.gitignore @@ -0,0 +1,2 @@ +asd.txt +asd.png diff --git a/examples/aoc2024/day24/notes.txt b/examples/aoc2024/day24/notes.txt new file mode 100644 index 00000000..c0451408 --- /dev/null +++ b/examples/aoc2024/day24/notes.txt @@ -0,0 +1,23 @@ +For all bits except the first and the last, there are 5 logic gates. + +InputXor: + Inputs: xnn, ynn (e.g. x05, y05) + Output: 1 if exactly one input is on, 0 if neither or both + +InputAnd: + Inputs: xnn, ynn (e.g. x05, y05) + Output: 1 if both inputs are on, 0 otherwise + +OutputXor (missing for first bit, output taken directly from InputXor) + Inputs: result of InputXor, result of previous OverflowXor + Output: value of znn + +OverflowAnd + Inputs: result of InputXor, overflow n-1 + Output: 1 if we overflow with inputs 1,0 or 0,1 due to carrying + 0 if we either don't overflow, or the overflow doesn't involve carrying + +OverflowOr + Inputs: result of InputAnd, result of OverflowAnd + Output: 1 if we overflow, either with input 1,1 or through OverflowAnd + 0 if we don't overflow diff --git a/examples/aoc2024/day24/part2.jou b/examples/aoc2024/day24/part2.jou new file mode 100644 index 00000000..04fc3a8e --- /dev/null +++ b/examples/aoc2024/day24/part2.jou @@ -0,0 +1,251 @@ +# The following commands can be used to create asd.png, which contains a nice +# picture of the input as a graph: +# +# echo 'digraph G {' > asd.txt +# grep -- '->' sampleinput.txt | awk '{print $1 "->" $5 " [label="$2"]\n"$3"->"$5" [label="$2"]"}' >> asd.txt +# echo '}' >> asd.txt +# dot -Tpng -o asd.png asd.txt + + +import "stdlib/str.jou" +import "stdlib/ascii.jou" +import "stdlib/io.jou" + + +# The sum calculator consists of 45 adders and outputs 46 bits z00,z01,...,z45. +# Each adder (except the first and last) consists of the following 5 logic gates. +enum GateType: + # Inputs: xnn, ynn (e.g. x05 and y05) + # Output: 1 if inputs are 1,0 or 0,1 + # 0 if inputs are 1,1 or 0,0 + # Purpose: Used for both output and overflow checking + InputXor + + # Inputs: xnn, ynn + # Output: 1 if inputs are 1,1 + # 0 if inputs are anything else + # Purpose: Used to detect overflow + InputAnd + + # Inputs: result of InputXor, carry bit from previous level + # Output: value of znn (the output bit of x+y) + # Purpose: Calculates the sum of the two numbers + OutputXor + + # Inputs: result of InputXor, carry bit from previous level + # Output: 1 if we overflow when adding bits 1,0 or 0,1 due to carrying + # 0 if there's no overflow, or the overflow happens due to input 1,1 + # Purpose: Creates intermediate value used when calculating overflow/carry + OverflowAnd + + # Inputs: result of InputAnd, result of OverflowAnd + # Output: 1 if we overflow in any way, due to input 1,1 or carrying + # 0 if there is no overflow + # Purpose: This is the overflow/carry bit sent to the next adder + OverflowOr + + +class Gate: + inputs: byte[4][2] + output: byte[4] + level: int + gtype: GateType + + def print(self) -> None: + if self->gtype == GateType::InputXor: + printf("InputXor") + elif self->gtype == GateType::InputAnd: + printf("InputAnd") + elif self->gtype == GateType::OutputXor: + printf("OutputXor") + elif self->gtype == GateType::OverflowAnd: + printf("OverflowAnd") + elif self->gtype == GateType::OverflowOr: + printf("OverflowOr") + else: + assert False + + printf(": %s XOR %s -> %s", self->inputs[0], self->inputs[1], self->output) + if self->level != -1: + printf(" [level=%d]", self->level) + printf("\n") + + +def check_gtype_counts(gates: Gate*, ngates: int) -> None: + counts = [0, 0, 0, 0, 0] + for i = 0; i < ngates; i++: + counts[gates[i].gtype as int]++ + + assert counts[InputXor + input_xors = 0 + input_ands = 0 + output_xors = 0 + overflow_ands = 0 + overflow_ors = 0 + + +# Due to carrying, every gate affects the last output z45 somehow. +# +def sort_gates_by_distance_from_z45 + + +# Determines how badly the sum machine seems to be wired. +# Zero is a machine that is working as expected. +def calculate_score(gates: Gate*, ngates: int) -> int: + input_xors: Gate*[45] + input_ands: Gate*[45] + # First adder only has input xors (used for output) and input ands (used for overflow/carry) + output_xors: Gate*[44] + overflow_ands: Gate*[44] + overflow_ors: Gate*[44] + + counts = [0, 0, 0, 0, 0] + for i = 0; i < ngates; i++: + counts[gates[i].gtype as int]++ + + assert counts[InputXor as int] == 45 + assert counts[InputAnd as int] == 45 + assert counts[OutputXor as int] == 44 + assert counts[OverflowAnd as int] == 44 + assert counts[OverflowOr as int] == 44 + + # Just put the things to the arrays, in any order + memset(counts, 0, sizeof(counts)) + + + for i = 0; i < ngates; i++: + if gates[i].gtype == GateType::InputXor: + input_xors + + +def goes_to_1_gate( + gates: Gate*, + ngates: int, + wire: byte*, + gtype: GateType, +) -> bool: + found = False + + for i = 0; i < ngates; i++: + if strcmp(gates[i].inputs[0], wire) == 0 or strcmp(gates[i].inputs[1], wire) == 0: + if gates[i].gtype != gtype: + return False + if found: + # it goes to two gates, both of the given type + return False + found = True + + return found + + +def goes_to_2_gates( + gates: Gate*, + ngates: int, + wire: byte*, + gtype1: GateType, + gtype2: GateType, +) -> bool: + assert gtype1 != gtype2 + + found1 = False + found2 = False + + for i = 0; i < ngates; i++: + if strcmp(gates[i].inputs[0], wire) == 0 or strcmp(gates[i].inputs[1], wire) == 0: + if gates[i].gtype == gtype1: + if found1: + return False + found1 = True + elif gates[i].gtype == gtype2: + if found2: + return False + found2 = True + else: + return False + + return found1 and found2 + + +def main() -> int: + gates: Gate[500] + ngates = 0 + + f = fopen("input", "r") + assert f != NULL + + line: byte[100] + while fgets(line, sizeof(line) as int, f) != NULL: + trim_ascii_whitespace(line) + if line[0] == '\0': + # end of initial values, start of logic gates + break + + g: Gate + op: byte[4] + while fscanf(f, "%3s %3s %3s -> %3s\n", &g.inputs[0], op, &g.inputs[1], &g.output) == 4: + # Inputs that don't come from other gates cannot be connected wrong, so + # the wiring mistakes don't mess this up. + if ( + (g.inputs[0][0] == 'x' and g.inputs[1][0] == 'y') + or (g.inputs[0][0] == 'y' and g.inputs[1][0] == 'x') + ): + # Handles input directly, so it must be InputXor or InputAnd + if strcmp(op, "XOR") == 0: + g.gtype = GateType::InputXor + elif strcmp(op, "AND") == 0: + g.gtype = GateType::InputAnd + else: + assert False + g.level = atoi(&g.inputs[0][1]) # Example: x05 --> 5 + else: + if strcmp(op, "XOR") == 0: + g.gtype = GateType::OutputXor + elif strcmp(op, "AND") == 0: + g.gtype = GateType::OverflowAnd + elif strcmp(op, "OR") == 0: + g.gtype = GateType::OverflowOr + else: + assert False + g.level = -1 # unknown + + assert ngates < sizeof(gates)/sizeof(gates[0]) + gates[ngates++] = g + + for i = 0; i < ngates; i++: + # Most InputAnd gates should connect to an OverflowOr. + if ( + gates[i].gtype == GateType::InputAnd + and gates[i].level != 0 + and not goes_to_1_gate(gates, ngates, gates[i].output, GateType::OverflowOr) + ): + printf("sus1 %s\n", gates[i].output) + + # First InputAnd produces the overflow bit directly, so it connects to + # the next OutputXor and OverflowAnd. + if ( + gates[i].gtype == GateType::InputAnd + and gates[i].level == 0 + and not goes_to_2_gates(gates, ngates, gates[i].output, GateType::OutputXor, GateType::OverflowAnd) + ): + printf("sus2 %s\n", gates[i].output) + + # Most InputXor gates should connect to an OutputXor and OverflowAnd. + if ( + gates[i].gtype == GateType::InputXor + and gates[i].level != 0 + and not goes_to_2_gates(gates, ngates, gates[i].output, GateType::OutputXor, GateType::OverflowAnd) + ): + printf("sus3 %s\n", gates[i].output) + + # First InputXor gate produces the first output directly. + if ( + gates[i].gtype == GateType::InputXor + and gates[i].level == 0 + and strcmp(gates[i].output, "z00") != 0 + ): + printf("sus4 %s\n", gates[i].output) + + + + fclose(f) + return 0 From c541675b3174e36abc72cefeab820a0e8f8b6dc8 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sun, 5 Jan 2025 00:56:58 +0200 Subject: [PATCH 03/12] score stuff --- examples/aoc2024/day24/part2.jou | 230 ++++++++++++++++--------------- 1 file changed, 120 insertions(+), 110 deletions(-) diff --git a/examples/aoc2024/day24/part2.jou b/examples/aoc2024/day24/part2.jou index 04fc3a8e..9f5edb47 100644 --- a/examples/aoc2024/day24/part2.jou +++ b/examples/aoc2024/day24/part2.jou @@ -6,8 +6,8 @@ # echo '}' >> asd.txt # dot -Tpng -o asd.png asd.txt - import "stdlib/str.jou" +import "stdlib/mem.jou" import "stdlib/ascii.jou" import "stdlib/io.jou" @@ -27,12 +27,12 @@ enum GateType: # Purpose: Used to detect overflow InputAnd - # Inputs: result of InputXor, carry bit from previous level + # Inputs: result of InputXor, overflow/carry bit from previous level # Output: value of znn (the output bit of x+y) # Purpose: Calculates the sum of the two numbers OutputXor - # Inputs: result of InputXor, carry bit from previous level + # Inputs: result of InputXor, overflow/carry bit from previous level # Output: 1 if we overflow when adding bits 1,0 or 0,1 due to carrying # 0 if there's no overflow, or the overflow happens due to input 1,1 # Purpose: Creates intermediate value used when calculating overflow/carry @@ -45,6 +45,16 @@ enum GateType: OverflowOr +# Special cases: +# +# - First adder doesn't need to handle overflow/carry from previous adders, +# so it only has InputXor and InputAnd. Result of InputXor is used for +# output and result of InputAnd is used for carry. +# +# - Last adder's OverflowOr outputs to z45, the last output bit. In other +# adders, it goes to the next adder's OutputXor and OverflowAnd. + + class Gate: inputs: byte[4][2] output: byte[4] @@ -71,103 +81,137 @@ class Gate: printf("\n") -def check_gtype_counts(gates: Gate*, ngates: int) -> None: - counts = [0, 0, 0, 0, 0] - for i = 0; i < ngates; i++: - counts[gates[i].gtype as int]++ +# By design, every gate uses the inputs somehow. +# We can use this to determine a somewhat reasonable ordering for the gates. +# +# Specifically, if x30 and y30 affect a gate, and inputs x31,y31,x32,y32,... +# don't, we assign it number 30, and then sort by affected values. +def sort_gates(gates: Gate*, ngates: int) -> None: + assert ngates < 250 + sort_values: int[250] + memset(sort_values, 0, sizeof(sort_values)) + + todo_max = 1000 + todo: byte[4]* = malloc(sizeof(todo[0]) * todo_max) + assert todo != NULL + + for inputnum = 0; inputnum < 45; inputnum++: + sprintf(todo[0], "x%02d", inputnum) # e.g. x06 + sprintf(todo[1], "x%02d", inputnum) # e.g. x06 + todo_len = 2 + + while todo_len > 0: + name: byte[4] = todo[--todo_len] + assert strlen(name) == 3 + + for i = 0; i < ngates; i++: + if strcmp(gates[i].inputs[0], name) == 0 or strcmp(gates[i].inputs[1], name) == 0: + sort_values[i] = inputnum + 1 # never zero + assert todo_len < todo_max + todo[todo_len++] = gates[i].output + + free(todo) + + # stupid sort algorithm + while True: + did_something = False + for i = 1; i < ngates; i++: + if sort_values[i-1] > sort_values[i]: + memswap(&sort_values[i-1], &sort_values[i], sizeof(sort_values[0])) + memswap(&gates[i-1], &gates[i], sizeof(gates[0])) + did_something = True + if not did_something: + break - assert counts[InputXor - input_xors = 0 - input_ands = 0 - output_xors = 0 - overflow_ands = 0 - overflow_ors = 0 +# Returns number of errors: 1 if connection is wrong, 0 if it is correct. +# g2g = gate to gate +def check_g2g(from: Gate*, to: Gate*) -> int: + if strcmp(from->output, to->inputs[0]) == 0 or strcmp(from->output, to->inputs[1]) == 0: + return 0 + else: + return 1 -# Due to carrying, every gate affects the last output z45 somehow. -# -def sort_gates_by_distance_from_z45 + +# Returns number of errors: 1 if connection is wrong, 0 if it is correct. +# g2o = gate to output +def check_g2o(from: Gate*, znum: int) -> int: + znn: byte[10] # e.g. z05 + sprintf(znn, "z%02d", znum) + if strcmp(from->output, znn) == 0: + return 0 + else: + return 1 # Determines how badly the sum machine seems to be wired. # Zero is a machine that is working as expected. -def calculate_score(gates: Gate*, ngates: int) -> int: - input_xors: Gate*[45] - input_ands: Gate*[45] - # First adder only has input xors (used for output) and input ands (used for overflow/carry) - output_xors: Gate*[44] - overflow_ands: Gate*[44] - overflow_ors: Gate*[44] +def calculate_score(orig_gates: Gate*, ngates: int) -> int: + # Make copy of input to avoid surprises (probably not necessary) + assert ngates <= 250 + gates: Gate[250] + memcpy(gates, orig_gates, ngates * sizeof(gates[0])) + + sort_gates(gates, ngates) + gate_arrays: Gate[45][5] counts = [0, 0, 0, 0, 0] for i = 0; i < ngates; i++: - counts[gates[i].gtype as int]++ + type_index = gates[i].gtype as int + assert counts[type_index] < 45 + gate_arrays[type_index][counts[type_index]++] = gates[i] - assert counts[InputXor as int] == 45 - assert counts[InputAnd as int] == 45 - assert counts[OutputXor as int] == 44 - assert counts[OverflowAnd as int] == 44 - assert counts[OverflowOr as int] == 44 + ixor = GateType::InputXor as int + iand = GateType::InputAnd as int + oxor = GateType::OutputXor as int + oand = GateType::OverflowAnd as int + oor = GateType::OverflowOr as int - # Just put the things to the arrays, in any order - memset(counts, 0, sizeof(counts)) - + # There are 45 adders, but the first adder is simpler than others. It only + # has two gates: InputXor for output, InputAnd for overflow/carry. + assert counts[ixor] == 45 + assert counts[iand] == 45 + assert counts[oxor] == 44 + assert counts[oand] == 44 + assert counts[oor] == 44 - for i = 0; i < ngates; i++: - if gates[i].gtype == GateType::InputXor: - input_xors + error_count = 0 + # First adder: InputXor for output + error_count += check_g2o(&gate_arrays[ixor][0], 0) -def goes_to_1_gate( - gates: Gate*, - ngates: int, - wire: byte*, - gtype: GateType, -) -> bool: - found = False + # First adder: carry into next OutputXor and OverflowAnd + error_count += check_g2g(&gate_arrays[iand][0], &gate_arrays[oxor][0]) + error_count += check_g2g(&gate_arrays[iand][0], &gate_arrays[oand][0]) - for i = 0; i < ngates; i++: - if strcmp(gates[i].inputs[0], wire) == 0 or strcmp(gates[i].inputs[1], wire) == 0: - if gates[i].gtype != gtype: - return False - if found: - # it goes to two gates, both of the given type - return False - found = True + for i = 1; i < 45; i++: + # Remaining adders: InputXor goes to OutputXor and OverflowAnd + error_count += check_g2g(&gate_arrays[ixor][i], &gate_arrays[oxor][i-1]) + error_count += check_g2g(&gate_arrays[ixor][i], &gate_arrays[oand][i-1]) - return found + # Remaining adders: InputAnd goes to OverflowOr + error_count += check_g2g(&gate_arrays[iand][i], &gate_arrays[oor][i-1]) + # Remaining adders: OutputXor computes the result + error_count += check_g2o(&gate_arrays[oxor][i-1], i) -def goes_to_2_gates( - gates: Gate*, - ngates: int, - wire: byte*, - gtype1: GateType, - gtype2: GateType, -) -> bool: - assert gtype1 != gtype2 + # Remaining adders: OverflowAnd goes to OverflowOr + error_count += check_g2g(&gate_arrays[oand][i-1], &gate_arrays[oor][i-1]) - found1 = False - found2 = False + if i != 44: + # Remaining adders except last: OverflowOr goes to next adder's + # OutputXor and OverflowAnd + error_count += check_g2g(&gate_arrays[oor][i-1], &gate_arrays[oxor][i]) + error_count += check_g2g(&gate_arrays[oor][i-1], &gate_arrays[oand][i]) - for i = 0; i < ngates; i++: - if strcmp(gates[i].inputs[0], wire) == 0 or strcmp(gates[i].inputs[1], wire) == 0: - if gates[i].gtype == gtype1: - if found1: - return False - found1 = True - elif gates[i].gtype == gtype2: - if found2: - return False - found2 = True - else: - return False + # Last adder: OverflowOr goes to last output (z45) + error_count += check_g2o(&gate_arrays[oxor][43], 45) - return found1 and found2 + return error_count def main() -> int: - gates: Gate[500] + gates: Gate[250] ngates = 0 f = fopen("input", "r") @@ -211,41 +255,7 @@ def main() -> int: assert ngates < sizeof(gates)/sizeof(gates[0]) gates[ngates++] = g - for i = 0; i < ngates; i++: - # Most InputAnd gates should connect to an OverflowOr. - if ( - gates[i].gtype == GateType::InputAnd - and gates[i].level != 0 - and not goes_to_1_gate(gates, ngates, gates[i].output, GateType::OverflowOr) - ): - printf("sus1 %s\n", gates[i].output) - - # First InputAnd produces the overflow bit directly, so it connects to - # the next OutputXor and OverflowAnd. - if ( - gates[i].gtype == GateType::InputAnd - and gates[i].level == 0 - and not goes_to_2_gates(gates, ngates, gates[i].output, GateType::OutputXor, GateType::OverflowAnd) - ): - printf("sus2 %s\n", gates[i].output) - - # Most InputXor gates should connect to an OutputXor and OverflowAnd. - if ( - gates[i].gtype == GateType::InputXor - and gates[i].level != 0 - and not goes_to_2_gates(gates, ngates, gates[i].output, GateType::OutputXor, GateType::OverflowAnd) - ): - printf("sus3 %s\n", gates[i].output) - - # First InputXor gate produces the first output directly. - if ( - gates[i].gtype == GateType::InputXor - and gates[i].level == 0 - and strcmp(gates[i].output, "z00") != 0 - ): - printf("sus4 %s\n", gates[i].output) - - + printf("%d\n", calculate_score(gates, ngates)) fclose(f) return 0 From 90c3394b6b8417d31911b5bab52e1058ad952de4 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sun, 5 Jan 2025 01:34:25 +0200 Subject: [PATCH 04/12] small test data working pretty good... --- examples/aoc2024/day24/part1.jou | 2 +- examples/aoc2024/day24/part2.jou | 96 +++++++++---------- .../{sampleinput.txt => sampleinput1.txt} | 0 .../aoc2024/day24/sampleinput2-correct.txt | 29 ++++++ examples/aoc2024/day24/sampleinput2-mixed.txt | 22 +++++ 5 files changed, 100 insertions(+), 49 deletions(-) rename examples/aoc2024/day24/{sampleinput.txt => sampleinput1.txt} (100%) create mode 100644 examples/aoc2024/day24/sampleinput2-correct.txt create mode 100644 examples/aoc2024/day24/sampleinput2-mixed.txt diff --git a/examples/aoc2024/day24/part1.jou b/examples/aoc2024/day24/part1.jou index 7878d05e..0156e3f7 100644 --- a/examples/aoc2024/day24/part1.jou +++ b/examples/aoc2024/day24/part1.jou @@ -86,7 +86,7 @@ def main() -> int: gates: LogicGate[500] ngates = 0 - f = fopen("input", "r") + f = fopen("sampleinput1.txt", "r") assert f != NULL line: byte[100] diff --git a/examples/aoc2024/day24/part2.jou b/examples/aoc2024/day24/part2.jou index 9f5edb47..7d8be770 100644 --- a/examples/aoc2024/day24/part2.jou +++ b/examples/aoc2024/day24/part2.jou @@ -44,42 +44,21 @@ enum GateType: # Purpose: This is the overflow/carry bit sent to the next adder OverflowOr - # Special cases: # # - First adder doesn't need to handle overflow/carry from previous adders, # so it only has InputXor and InputAnd. Result of InputXor is used for # output and result of InputAnd is used for carry. # -# - Last adder's OverflowOr outputs to z45, the last output bit. In other +# - Last adder's OverflowOr outputs to the last output bit (z45). In other # adders, it goes to the next adder's OutputXor and OverflowAnd. class Gate: inputs: byte[4][2] output: byte[4] - level: int gtype: GateType - def print(self) -> None: - if self->gtype == GateType::InputXor: - printf("InputXor") - elif self->gtype == GateType::InputAnd: - printf("InputAnd") - elif self->gtype == GateType::OutputXor: - printf("OutputXor") - elif self->gtype == GateType::OverflowAnd: - printf("OverflowAnd") - elif self->gtype == GateType::OverflowOr: - printf("OverflowOr") - else: - assert False - - printf(": %s XOR %s -> %s", self->inputs[0], self->inputs[1], self->output) - if self->level != -1: - printf(" [level=%d]", self->level) - printf("\n") - # By design, every gate uses the inputs somehow. # We can use this to determine a somewhat reasonable ordering for the gates. @@ -146,7 +125,7 @@ def check_g2o(from: Gate*, znum: int) -> int: # Determines how badly the sum machine seems to be wired. # Zero is a machine that is working as expected. -def calculate_score(orig_gates: Gate*, ngates: int) -> int: +def count_errors(orig_gates: Gate*, ngates: int) -> int: # Make copy of input to avoid surprises (probably not necessary) assert ngates <= 250 gates: Gate[250] @@ -167,13 +146,14 @@ def calculate_score(orig_gates: Gate*, ngates: int) -> int: oand = GateType::OverflowAnd as int oor = GateType::OverflowOr as int - # There are 45 adders, but the first adder is simpler than others. It only - # has two gates: InputXor for output, InputAnd for overflow/carry. - assert counts[ixor] == 45 - assert counts[iand] == 45 - assert counts[oxor] == 44 - assert counts[oand] == 44 - assert counts[oor] == 44 + # With real input, there are 45 adders, but the first adder is simpler than others. + # It only has two gates: InputXor for output, InputAnd for overflow/carry. + num_adders = ngates/5 + 1 + assert counts[ixor] == num_adders + assert counts[iand] == num_adders + assert counts[oxor] == num_adders - 1 + assert counts[oand] == num_adders - 1 + assert counts[oor] == num_adders - 1 error_count = 0 @@ -184,7 +164,7 @@ def calculate_score(orig_gates: Gate*, ngates: int) -> int: error_count += check_g2g(&gate_arrays[iand][0], &gate_arrays[oxor][0]) error_count += check_g2g(&gate_arrays[iand][0], &gate_arrays[oand][0]) - for i = 1; i < 45; i++: + for i = 1; i < num_adders; i++: # Remaining adders: InputXor goes to OutputXor and OverflowAnd error_count += check_g2g(&gate_arrays[ixor][i], &gate_arrays[oxor][i-1]) error_count += check_g2g(&gate_arrays[ixor][i], &gate_arrays[oand][i-1]) @@ -198,35 +178,47 @@ def calculate_score(orig_gates: Gate*, ngates: int) -> int: # Remaining adders: OverflowAnd goes to OverflowOr error_count += check_g2g(&gate_arrays[oand][i-1], &gate_arrays[oor][i-1]) - if i != 44: + if i != num_adders-1: # Remaining adders except last: OverflowOr goes to next adder's # OutputXor and OverflowAnd error_count += check_g2g(&gate_arrays[oor][i-1], &gate_arrays[oxor][i]) error_count += check_g2g(&gate_arrays[oor][i-1], &gate_arrays[oand][i]) - # Last adder: OverflowOr goes to last output (z45) - error_count += check_g2o(&gate_arrays[oxor][43], 45) + # Last adder: OverflowOr goes to last output (z45 with real input) + error_count += check_g2o(&gate_arrays[oor][num_adders-2], num_adders) return error_count -def main() -> int: +def read_gates(filename: byte*, ngates: int*) -> Gate[250]: gates: Gate[250] - ngates = 0 + memset(gates, 0, sizeof(gates)) # TODO: why is this needed to prevent compiler warning? + *ngates = 0 - f = fopen("input", "r") + f = fopen(filename, "r") assert f != NULL line: byte[100] while fgets(line, sizeof(line) as int, f) != NULL: + # Remove comments, if any. I wrote test file by hand. + if strstr(line, "#") != NULL: + *strstr(line, "#") = '\0' + trim_ascii_whitespace(line) + + # ignore blanks and comment-only lines if line[0] == '\0': - # end of initial values, start of logic gates - break + continue + + # AoC input files have unnecessary lines at start, e.g. "x01: 1" + if strstr(line, ":") != NULL: + continue + + g: Gate + op: byte[4] + n = sscanf(line, "%3s %3s %3s -> %3s", &g.inputs[0], op, &g.inputs[1], &g.output) + assert n == 4 - g: Gate - op: byte[4] - while fscanf(f, "%3s %3s %3s -> %3s\n", &g.inputs[0], op, &g.inputs[1], &g.output) == 4: # Inputs that don't come from other gates cannot be connected wrong, so # the wiring mistakes don't mess this up. if ( @@ -240,7 +232,6 @@ def main() -> int: g.gtype = GateType::InputAnd else: assert False - g.level = atoi(&g.inputs[0][1]) # Example: x05 --> 5 else: if strcmp(op, "XOR") == 0: g.gtype = GateType::OutputXor @@ -250,12 +241,21 @@ def main() -> int: g.gtype = GateType::OverflowOr else: assert False - g.level = -1 # unknown - assert ngates < sizeof(gates)/sizeof(gates[0]) - gates[ngates++] = g - - printf("%d\n", calculate_score(gates, ngates)) + assert *ngates < sizeof(gates)/sizeof(gates[0]) + gates[(*ngates)++] = g fclose(f) + return gates + + +def main() -> int: + # Ensure that counting errors works + ngates: int + gates = read_gates("sampleinput2-correct.txt", &ngates) + printf("%d\n", count_errors(gates, ngates)) + assert count_errors(gates, ngates) == 0 + + + return 0 diff --git a/examples/aoc2024/day24/sampleinput.txt b/examples/aoc2024/day24/sampleinput1.txt similarity index 100% rename from examples/aoc2024/day24/sampleinput.txt rename to examples/aoc2024/day24/sampleinput1.txt diff --git a/examples/aoc2024/day24/sampleinput2-correct.txt b/examples/aoc2024/day24/sampleinput2-correct.txt new file mode 100644 index 00000000..170963c4 --- /dev/null +++ b/examples/aoc2024/day24/sampleinput2-correct.txt @@ -0,0 +1,29 @@ +# Advent of Code doesn't provide very nice test files for part 2. +# +# This file (written by me) defines a working sum machine. It takes 4-bit +# inputs x00,...,x03 and y00,...,y03, and outputs a 5-bit sum in z00,...,z04. + +# First adder +x00 XOR y00 -> z00 # output +x00 AND y00 -> co0 # co = carry out + +# Second adder +x01 XOR y01 -> ix1 # ix = InputXor +x01 AND y01 -> ia1 # ia = InputAnd +ix1 XOR co0 -> z01 # output +ix1 AND co0 -> oa1 # oa = OverflowAnd +ia1 OR oa1 -> co1 # co = carry out + +# Third adder +x02 XOR y02 -> ix2 +x02 AND y02 -> ia2 +ix2 XOR co1 -> z02 +ix2 AND co1 -> oa2 +ia2 OR oa2 -> co2 + +# Fourth (last) adder +x03 XOR y03 -> ix3 +x03 AND y03 -> ia3 +ix3 XOR co2 -> z03 +ix3 AND co2 -> oa3 +ia3 OR oa3 -> z04 # carry goes into last result bit diff --git a/examples/aoc2024/day24/sampleinput2-mixed.txt b/examples/aoc2024/day24/sampleinput2-mixed.txt new file mode 100644 index 00000000..cd0fc65f --- /dev/null +++ b/examples/aoc2024/day24/sampleinput2-mixed.txt @@ -0,0 +1,22 @@ +# Same as sampleinput2-correct.txt, but two pairs of outputs has been swapped. + +x00 XOR y00 -> z00 +x00 AND y00 -> co0 + +x01 XOR y01 -> ix1 +x01 AND y01 -> ix3 # SWAPPED A +ix1 XOR co0 -> z01 +ix1 AND co0 -> oa1 +ia1 OR oa1 -> co1 + +x02 XOR y02 -> ia2 # SWAPPED B +x02 AND y02 -> ix2 # SWAPPED B +ix2 XOR co1 -> z02 +ix2 AND co1 -> oa2 +ia2 OR oa2 -> co2 + +x03 XOR y03 -> ia1 # SWAPPED A +x03 AND y03 -> ia3 +ix3 XOR co2 -> z03 +ix3 AND co2 -> oa3 +ia3 OR oa3 -> z04 From 179f8ce8d5fab957d8b7d68aa262adf062303dd3 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sun, 5 Jan 2025 02:09:04 +0200 Subject: [PATCH 05/12] basically done? --- examples/aoc2024/day24/part2.jou | 107 +++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 7 deletions(-) diff --git a/examples/aoc2024/day24/part2.jou b/examples/aoc2024/day24/part2.jou index 7d8be770..21838955 100644 --- a/examples/aoc2024/day24/part2.jou +++ b/examples/aoc2024/day24/part2.jou @@ -70,25 +70,38 @@ def sort_gates(gates: Gate*, ngates: int) -> None: sort_values: int[250] memset(sort_values, 0, sizeof(sort_values)) - todo_max = 1000 - todo: byte[4]* = malloc(sizeof(todo[0]) * todo_max) + maxlen = 1000 + todo: byte[4]* = malloc(sizeof(todo[0]) * maxlen) + done: byte[4]* = malloc(sizeof(done[0]) * maxlen) assert todo != NULL + assert done != NULL for inputnum = 0; inputnum < 45; inputnum++: sprintf(todo[0], "x%02d", inputnum) # e.g. x06 sprintf(todo[1], "x%02d", inputnum) # e.g. x06 todo_len = 2 + done_len = 0 while todo_len > 0: name: byte[4] = todo[--todo_len] assert strlen(name) == 3 + found = False + for i = 0; i < done_len; i++: + if strcmp(done[i], name) == 0: + found = True + if found: + continue + for i = 0; i < ngates; i++: if strcmp(gates[i].inputs[0], name) == 0 or strcmp(gates[i].inputs[1], name) == 0: sort_values[i] = inputnum + 1 # never zero - assert todo_len < todo_max + assert todo_len < maxlen todo[todo_len++] = gates[i].output + assert done_len < maxlen + done[done_len++] = name + free(todo) # stupid sort algorithm @@ -103,6 +116,18 @@ def sort_gates(gates: Gate*, ngates: int) -> None: break +def sort_strings(strings: byte**, nstrings: int) -> None: + # stupid sort algorithm + while True: + did_something = False + for i = 1; i < nstrings; i++: + if strcmp(strings[i-1], strings[i]) > 0: + memswap(&strings[i-1], &strings[i], sizeof(strings[0])) + did_something = True + if not did_something: + break + + # Returns number of errors: 1 if connection is wrong, 0 if it is correct. # g2g = gate to gate def check_g2g(from: Gate*, to: Gate*) -> int: @@ -249,13 +274,81 @@ def read_gates(filename: byte*, ngates: int*) -> Gate[250]: return gates -def main() -> int: - # Ensure that counting errors works +# Ensures that counting errors returns 0 when there are no errors. +def test_counting() -> None: ngates: int gates = read_gates("sampleinput2-correct.txt", &ngates) - printf("%d\n", count_errors(gates, ngates)) assert count_errors(gates, ngates) == 0 - + +def fix_order_of_outputs(gates: Gate*, ngates: int, progress_prints: bool) -> None: + while count_errors(gates, ngates) > 0: + if progress_prints: + printf("---------- There are %d errors. ----------\n", count_errors(gates, ngates)) + fflush(stdout) + + # Swap two gates so that error count decreases as much as possible + best_g1: Gate* = NULL + best_g2: Gate* = NULL + best_count = -1 + + for g1 = &gates[0]; g1 < &gates[ngates]; g1++: + if progress_prints: + printf("%lld/%d", (((g1 as long) - (&gates[0] as long)) / sizeof(gates[0])) as int, ngates) + fflush(stdout) + + for g2 = &g1[1]; g2 < &gates[ngates]; g2++: + memswap(&g1->output, &g2->output, sizeof(g1->output)) + count = count_errors(gates, ngates) + memswap(&g1->output, &g2->output, sizeof(g1->output)) + + if progress_prints: + printf(".") + fflush(stdout) + + if best_count == -1 or count < best_count: + if progress_prints: + printf("[%d]", count) + fflush(stdout) + + best_count = count + best_g1 = g1 + best_g2 = g2 + + if progress_prints: + printf("\n") + fflush(stdout) + + assert best_g1 != NULL + assert best_g2 != NULL + memswap(&best_g1->output, &best_g2->output, sizeof(best_g1->output)) + + +def main() -> int: + test_counting() + + ngates: int + gates = read_gates("sampleinput2-mixed.txt", &ngates) + orig_gates = gates + + fix_order_of_outputs(gates, ngates, False) + + bad_outputs: byte*[10] + bad_outputs_len = 0 + for i = 0; i < ngates; i++: + if strcmp(orig_gates[i].output, gates[i].output) != 0: + assert bad_outputs_len < 10 + bad_outputs[bad_outputs_len++] = orig_gates[i].output + + # Output: 4 gates were in the wrong place (2 swaps) + printf("%d gates were in the wrong place (%d swaps)\n", bad_outputs_len, bad_outputs_len / 2) + + # Output: ia1,ia2,ix2,ix3 + sort_strings(bad_outputs, bad_outputs_len) + for i = 0; i < bad_outputs_len; i++: + if i != 0: + printf(",") + printf("%s", bad_outputs[i]) + printf("\n") return 0 From 97a7a1e43da5a33c3334ca430643c463995a2629 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sun, 5 Jan 2025 02:34:53 +0200 Subject: [PATCH 06/12] tweak --- examples/aoc2024/day24/part2.jou | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/examples/aoc2024/day24/part2.jou b/examples/aoc2024/day24/part2.jou index 21838955..b4acb043 100644 --- a/examples/aoc2024/day24/part2.jou +++ b/examples/aoc2024/day24/part2.jou @@ -70,15 +70,13 @@ def sort_gates(gates: Gate*, ngates: int) -> None: sort_values: int[250] memset(sort_values, 0, sizeof(sort_values)) - maxlen = 1000 - todo: byte[4]* = malloc(sizeof(todo[0]) * maxlen) - done: byte[4]* = malloc(sizeof(done[0]) * maxlen) - assert todo != NULL - assert done != NULL + maxlen = 400 + todo: byte[4][400] + done: byte[4][400] for inputnum = 0; inputnum < 45; inputnum++: sprintf(todo[0], "x%02d", inputnum) # e.g. x06 - sprintf(todo[1], "x%02d", inputnum) # e.g. x06 + sprintf(todo[1], "y%02d", inputnum) # e.g. y06 todo_len = 2 done_len = 0 @@ -102,8 +100,6 @@ def sort_gates(gates: Gate*, ngates: int) -> None: assert done_len < maxlen done[done_len++] = name - free(todo) - # stupid sort algorithm while True: did_something = False @@ -151,11 +147,13 @@ def check_g2o(from: Gate*, znum: int) -> int: # Determines how badly the sum machine seems to be wired. # Zero is a machine that is working as expected. def count_errors(orig_gates: Gate*, ngates: int) -> int: - # Make copy of input to avoid surprises (probably not necessary) + # Make copy of input to avoid surprises. Can mess up indexing in surprising + # ways if we don't do this. assert ngates <= 250 gates: Gate[250] memcpy(gates, orig_gates, ngates * sizeof(gates[0])) + # Sort copy, not original. sort_gates(gates, ngates) gate_arrays: Gate[45][5] From 4658899bbc340c31cecc3496f6b14255064db8dc Mon Sep 17 00:00:00 2001 From: Akuli Date: Sun, 5 Jan 2025 02:39:36 +0200 Subject: [PATCH 07/12] add comment --- examples/aoc2024/day24/part2.jou | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/aoc2024/day24/part2.jou b/examples/aoc2024/day24/part2.jou index b4acb043..76c16a72 100644 --- a/examples/aoc2024/day24/part2.jou +++ b/examples/aoc2024/day24/part2.jou @@ -329,7 +329,7 @@ def main() -> int: gates = read_gates("sampleinput2-mixed.txt", &ngates) orig_gates = gates - fix_order_of_outputs(gates, ngates, False) + fix_order_of_outputs(gates, ngates, False) # use True instead of False for actual input bad_outputs: byte*[10] bad_outputs_len = 0 From cf45eaae08809fbb4060e506b5f4611be57f6a35 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sun, 5 Jan 2025 02:42:02 +0200 Subject: [PATCH 08/12] Enable progress prints --- examples/aoc2024/day24/part2.jou | 65 +++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/examples/aoc2024/day24/part2.jou b/examples/aoc2024/day24/part2.jou index 76c16a72..4d889aa2 100644 --- a/examples/aoc2024/day24/part2.jou +++ b/examples/aoc2024/day24/part2.jou @@ -279,11 +279,10 @@ def test_counting() -> None: assert count_errors(gates, ngates) == 0 -def fix_order_of_outputs(gates: Gate*, ngates: int, progress_prints: bool) -> None: +def fix_order_of_outputs(gates: Gate*, ngates: int) -> None: while count_errors(gates, ngates) > 0: - if progress_prints: - printf("---------- There are %d errors. ----------\n", count_errors(gates, ngates)) - fflush(stdout) + printf("---------- There are %d errors. ----------\n", count_errors(gates, ngates)) + fflush(stdout) # Swap two gates so that error count decreases as much as possible best_g1: Gate* = NULL @@ -291,31 +290,27 @@ def fix_order_of_outputs(gates: Gate*, ngates: int, progress_prints: bool) -> No best_count = -1 for g1 = &gates[0]; g1 < &gates[ngates]; g1++: - if progress_prints: - printf("%lld/%d", (((g1 as long) - (&gates[0] as long)) / sizeof(gates[0])) as int, ngates) - fflush(stdout) + printf("%lld/%d", (((g1 as long) - (&gates[0] as long)) / sizeof(gates[0])) as int, ngates) + fflush(stdout) for g2 = &g1[1]; g2 < &gates[ngates]; g2++: memswap(&g1->output, &g2->output, sizeof(g1->output)) count = count_errors(gates, ngates) memswap(&g1->output, &g2->output, sizeof(g1->output)) - if progress_prints: - printf(".") - fflush(stdout) + printf(".") + fflush(stdout) if best_count == -1 or count < best_count: - if progress_prints: - printf("[%d]", count) - fflush(stdout) + printf("[%d]", count) + fflush(stdout) best_count = count best_g1 = g1 best_g2 = g2 - if progress_prints: - printf("\n") - fflush(stdout) + printf("\n") + fflush(stdout) assert best_g1 != NULL assert best_g2 != NULL @@ -329,7 +324,43 @@ def main() -> int: gates = read_gates("sampleinput2-mixed.txt", &ngates) orig_gates = gates - fix_order_of_outputs(gates, ngates, False) # use True instead of False for actual input + # Output: ---------- There are 6 errors. ---------- + # Output: 0/17.[9]..[7]............. + # Output: 1/17.[6].............. + # Output: 2/17.............. + # Output: 3/17.........[3].... + # Output: 4/17............ + # Output: 5/17........... + # Output: 6/17.......... + # Output: 7/17......... + # Output: 8/17........ + # Output: 9/17....... + # Output: 10/17...... + # Output: 11/17..... + # Output: 12/17.... + # Output: 13/17... + # Output: 14/17.. + # Output: 15/17. + # Output: 16/17 + # Output: ---------- There are 3 errors. ---------- + # Output: 0/17.[6]..[5]....[4]......... + # Output: 1/17.[3].............. + # Output: 2/17.............. + # Output: 3/17............. + # Output: 4/17............ + # Output: 5/17........... + # Output: 6/17.......... + # Output: 7/17.[0]........ + # Output: 8/17........ + # Output: 9/17....... + # Output: 10/17...... + # Output: 11/17..... + # Output: 12/17.... + # Output: 13/17... + # Output: 14/17.. + # Output: 15/17. + # Output: 16/17 + fix_order_of_outputs(gates, ngates) bad_outputs: byte*[10] bad_outputs_len = 0 From 3e01c81fcc7429e30ee3466a0a92939c1ad1f2a1 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sun, 5 Jan 2025 02:55:26 +0200 Subject: [PATCH 09/12] avoid 'Output:' as it confuses jou test --- examples/aoc2024/day24/part2.jou | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/aoc2024/day24/part2.jou b/examples/aoc2024/day24/part2.jou index 4d889aa2..426b181a 100644 --- a/examples/aoc2024/day24/part2.jou +++ b/examples/aoc2024/day24/part2.jou @@ -16,30 +16,30 @@ import "stdlib/io.jou" # Each adder (except the first and last) consists of the following 5 logic gates. enum GateType: # Inputs: xnn, ynn (e.g. x05 and y05) - # Output: 1 if inputs are 1,0 or 0,1 + # Result: 1 if inputs are 1,0 or 0,1 # 0 if inputs are 1,1 or 0,0 # Purpose: Used for both output and overflow checking InputXor # Inputs: xnn, ynn - # Output: 1 if inputs are 1,1 + # Result: 1 if inputs are 1,1 # 0 if inputs are anything else # Purpose: Used to detect overflow InputAnd # Inputs: result of InputXor, overflow/carry bit from previous level - # Output: value of znn (the output bit of x+y) + # Result: value of znn (the output bit of x+y) # Purpose: Calculates the sum of the two numbers OutputXor # Inputs: result of InputXor, overflow/carry bit from previous level - # Output: 1 if we overflow when adding bits 1,0 or 0,1 due to carrying + # Result: 1 if we overflow when adding bits 1,0 or 0,1 due to carrying # 0 if there's no overflow, or the overflow happens due to input 1,1 # Purpose: Creates intermediate value used when calculating overflow/carry OverflowAnd # Inputs: result of InputAnd, result of OverflowAnd - # Output: 1 if we overflow in any way, due to input 1,1 or carrying + # Result: 1 if we overflow in any way, due to input 1,1 or carrying # 0 if there is no overflow # Purpose: This is the overflow/carry bit sent to the next adder OverflowOr From 2a9ffe2cd16eb0f505361f8ddbad097ca1d1e013 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sun, 5 Jan 2025 02:57:22 +0200 Subject: [PATCH 10/12] Delete notes --- examples/aoc2024/day24/notes.txt | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 examples/aoc2024/day24/notes.txt diff --git a/examples/aoc2024/day24/notes.txt b/examples/aoc2024/day24/notes.txt deleted file mode 100644 index c0451408..00000000 --- a/examples/aoc2024/day24/notes.txt +++ /dev/null @@ -1,23 +0,0 @@ -For all bits except the first and the last, there are 5 logic gates. - -InputXor: - Inputs: xnn, ynn (e.g. x05, y05) - Output: 1 if exactly one input is on, 0 if neither or both - -InputAnd: - Inputs: xnn, ynn (e.g. x05, y05) - Output: 1 if both inputs are on, 0 otherwise - -OutputXor (missing for first bit, output taken directly from InputXor) - Inputs: result of InputXor, result of previous OverflowXor - Output: value of znn - -OverflowAnd - Inputs: result of InputXor, overflow n-1 - Output: 1 if we overflow with inputs 1,0 or 0,1 due to carrying - 0 if we either don't overflow, or the overflow doesn't involve carrying - -OverflowOr - Inputs: result of InputAnd, result of OverflowAnd - Output: 1 if we overflow, either with input 1,1 or through OverflowAnd - 0 if we don't overflow From 2d617163e3d721d8b8e47961c841c8f87124031b Mon Sep 17 00:00:00 2001 From: Akuli Date: Sun, 5 Jan 2025 10:22:48 +0200 Subject: [PATCH 11/12] optimize --- examples/aoc2024/day24/part2.jou | 156 +++++++++++++------------------ 1 file changed, 65 insertions(+), 91 deletions(-) diff --git a/examples/aoc2024/day24/part2.jou b/examples/aoc2024/day24/part2.jou index 426b181a..16956c0c 100644 --- a/examples/aoc2024/day24/part2.jou +++ b/examples/aoc2024/day24/part2.jou @@ -59,13 +59,17 @@ class Gate: output: byte[4] gtype: GateType + # Gate is sus, if something seems to be wrong with it and we should try to + # swap its output with others. + sus: bool + # By design, every gate uses the inputs somehow. # We can use this to determine a somewhat reasonable ordering for the gates. # # Specifically, if x30 and y30 affect a gate, and inputs x31,y31,x32,y32,... # don't, we assign it number 30, and then sort by affected values. -def sort_gates(gates: Gate*, ngates: int) -> None: +def sort_gates(gates: Gate**, ngates: int) -> None: assert ngates < 250 sort_values: int[250] memset(sort_values, 0, sizeof(sort_values)) @@ -92,10 +96,10 @@ def sort_gates(gates: Gate*, ngates: int) -> None: continue for i = 0; i < ngates; i++: - if strcmp(gates[i].inputs[0], name) == 0 or strcmp(gates[i].inputs[1], name) == 0: + if strcmp(gates[i]->inputs[0], name) == 0 or strcmp(gates[i]->inputs[1], name) == 0: sort_values[i] = inputnum + 1 # never zero assert todo_len < maxlen - todo[todo_len++] = gates[i].output + todo[todo_len++] = gates[i]->output assert done_len < maxlen done[done_len++] = name @@ -126,89 +130,92 @@ def sort_strings(strings: byte**, nstrings: int) -> None: # Returns number of errors: 1 if connection is wrong, 0 if it is correct. # g2g = gate to gate -def check_g2g(from: Gate*, to: Gate*) -> int: +def check_g2g(from: Gate*, to: Gate*, mark_sus: bool) -> int: if strcmp(from->output, to->inputs[0]) == 0 or strcmp(from->output, to->inputs[1]) == 0: return 0 else: + if mark_sus: + from->sus = True + to->sus = True return 1 # Returns number of errors: 1 if connection is wrong, 0 if it is correct. # g2o = gate to output -def check_g2o(from: Gate*, znum: int) -> int: +def check_g2o(from: Gate*, znum: int, mark_sus: bool) -> int: znn: byte[10] # e.g. z05 sprintf(znn, "z%02d", znum) if strcmp(from->output, znn) == 0: return 0 else: + if mark_sus: + from->sus = True return 1 # Determines how badly the sum machine seems to be wired. # Zero is a machine that is working as expected. -def count_errors(orig_gates: Gate*, ngates: int) -> int: - # Make copy of input to avoid surprises. Can mess up indexing in surprising - # ways if we don't do this. +def count_errors(orig_gates: Gate*, ngates: int, mark_sus: bool) -> int: assert ngates <= 250 - gates: Gate[250] - memcpy(gates, orig_gates, ngates * sizeof(gates[0])) + gateptrs: Gate*[250] + for i = 0; i < ngates; i++: + gateptrs[i] = &orig_gates[i] - # Sort copy, not original. - sort_gates(gates, ngates) + sort_gates(gateptrs, ngates) - gate_arrays: Gate[45][5] + gate_arrays: Gate*[45][5] counts = [0, 0, 0, 0, 0] for i = 0; i < ngates; i++: - type_index = gates[i].gtype as int + type_index = gateptrs[i]->gtype as int assert counts[type_index] < 45 - gate_arrays[type_index][counts[type_index]++] = gates[i] - - ixor = GateType::InputXor as int - iand = GateType::InputAnd as int - oxor = GateType::OutputXor as int - oand = GateType::OverflowAnd as int - oor = GateType::OverflowOr as int + gate_arrays[type_index][counts[type_index]++] = gateptrs[i] # With real input, there are 45 adders, but the first adder is simpler than others. # It only has two gates: InputXor for output, InputAnd for overflow/carry. num_adders = ngates/5 + 1 - assert counts[ixor] == num_adders - assert counts[iand] == num_adders - assert counts[oxor] == num_adders - 1 - assert counts[oand] == num_adders - 1 - assert counts[oor] == num_adders - 1 + assert counts[GateType::InputXor as int] == num_adders + assert counts[GateType::InputAnd as int] == num_adders + assert counts[GateType::OutputXor as int] == num_adders - 1 + assert counts[GateType::OverflowAnd as int] == num_adders - 1 + assert counts[GateType::OverflowOr as int] == num_adders - 1 + + ixors: Gate** = gate_arrays[GateType::InputXor as int] + iands: Gate** = gate_arrays[GateType::InputAnd as int] + oxors: Gate** = gate_arrays[GateType::OutputXor as int] + oands: Gate** = gate_arrays[GateType::OverflowAnd as int] + oors: Gate** = gate_arrays[GateType::OverflowOr as int] error_count = 0 # First adder: InputXor for output - error_count += check_g2o(&gate_arrays[ixor][0], 0) + error_count += check_g2o(ixors[0], 0, mark_sus) # First adder: carry into next OutputXor and OverflowAnd - error_count += check_g2g(&gate_arrays[iand][0], &gate_arrays[oxor][0]) - error_count += check_g2g(&gate_arrays[iand][0], &gate_arrays[oand][0]) + error_count += check_g2g(iands[0], oxors[0], mark_sus) + error_count += check_g2g(iands[0], oands[0], mark_sus) for i = 1; i < num_adders; i++: # Remaining adders: InputXor goes to OutputXor and OverflowAnd - error_count += check_g2g(&gate_arrays[ixor][i], &gate_arrays[oxor][i-1]) - error_count += check_g2g(&gate_arrays[ixor][i], &gate_arrays[oand][i-1]) + error_count += check_g2g(ixors[i], oxors[i-1], mark_sus) + error_count += check_g2g(ixors[i], oands[i-1], mark_sus) # Remaining adders: InputAnd goes to OverflowOr - error_count += check_g2g(&gate_arrays[iand][i], &gate_arrays[oor][i-1]) + error_count += check_g2g(iands[i], oors[i-1], mark_sus) # Remaining adders: OutputXor computes the result - error_count += check_g2o(&gate_arrays[oxor][i-1], i) + error_count += check_g2o(oxors[i-1], i, mark_sus) # Remaining adders: OverflowAnd goes to OverflowOr - error_count += check_g2g(&gate_arrays[oand][i-1], &gate_arrays[oor][i-1]) + error_count += check_g2g(oands[i-1], oors[i-1], mark_sus) if i != num_adders-1: # Remaining adders except last: OverflowOr goes to next adder's # OutputXor and OverflowAnd - error_count += check_g2g(&gate_arrays[oor][i-1], &gate_arrays[oxor][i]) - error_count += check_g2g(&gate_arrays[oor][i-1], &gate_arrays[oand][i]) + error_count += check_g2g(oors[i-1], oxors[i], mark_sus) + error_count += check_g2g(oors[i-1], oands[i], mark_sus) # Last adder: OverflowOr goes to last output (z45 with real input) - error_count += check_g2o(&gate_arrays[oor][num_adders-2], num_adders) + error_count += check_g2o(oors[num_adders-2], num_adders, mark_sus) return error_count @@ -276,13 +283,22 @@ def read_gates(filename: byte*, ngates: int*) -> Gate[250]: def test_counting() -> None: ngates: int gates = read_gates("sampleinput2-correct.txt", &ngates) - assert count_errors(gates, ngates) == 0 + assert count_errors(gates, ngates, False) == 0 def fix_order_of_outputs(gates: Gate*, ngates: int) -> None: - while count_errors(gates, ngates) > 0: - printf("---------- There are %d errors. ----------\n", count_errors(gates, ngates)) - fflush(stdout) + while True: + for i = 0; i < ngates; i++: + gates[i].sus = False + num_errors = count_errors(gates, ngates, True) + + if num_errors == 0: + break + + nsus = 0 + for i = 0; i < ngates; i++: + if gates[i].sus: + nsus++ # Swap two gates so that error count decreases as much as possible best_g1: Gate* = NULL @@ -290,28 +306,22 @@ def fix_order_of_outputs(gates: Gate*, ngates: int) -> None: best_count = -1 for g1 = &gates[0]; g1 < &gates[ngates]; g1++: - printf("%lld/%d", (((g1 as long) - (&gates[0] as long)) / sizeof(gates[0])) as int, ngates) - fflush(stdout) + if not g1->sus: + continue for g2 = &g1[1]; g2 < &gates[ngates]; g2++: + if not g2->sus: + continue + memswap(&g1->output, &g2->output, sizeof(g1->output)) - count = count_errors(gates, ngates) + count = count_errors(gates, ngates, False) memswap(&g1->output, &g2->output, sizeof(g1->output)) - printf(".") - fflush(stdout) - if best_count == -1 or count < best_count: - printf("[%d]", count) - fflush(stdout) - best_count = count best_g1 = g1 best_g2 = g2 - printf("\n") - fflush(stdout) - assert best_g1 != NULL assert best_g2 != NULL memswap(&best_g1->output, &best_g2->output, sizeof(best_g1->output)) @@ -321,45 +331,9 @@ def main() -> int: test_counting() ngates: int - gates = read_gates("sampleinput2-mixed.txt", &ngates) - orig_gates = gates + gates = read_gates("input", &ngates) - # Output: ---------- There are 6 errors. ---------- - # Output: 0/17.[9]..[7]............. - # Output: 1/17.[6].............. - # Output: 2/17.............. - # Output: 3/17.........[3].... - # Output: 4/17............ - # Output: 5/17........... - # Output: 6/17.......... - # Output: 7/17......... - # Output: 8/17........ - # Output: 9/17....... - # Output: 10/17...... - # Output: 11/17..... - # Output: 12/17.... - # Output: 13/17... - # Output: 14/17.. - # Output: 15/17. - # Output: 16/17 - # Output: ---------- There are 3 errors. ---------- - # Output: 0/17.[6]..[5]....[4]......... - # Output: 1/17.[3].............. - # Output: 2/17.............. - # Output: 3/17............. - # Output: 4/17............ - # Output: 5/17........... - # Output: 6/17.......... - # Output: 7/17.[0]........ - # Output: 8/17........ - # Output: 9/17....... - # Output: 10/17...... - # Output: 11/17..... - # Output: 12/17.... - # Output: 13/17... - # Output: 14/17.. - # Output: 15/17. - # Output: 16/17 + orig_gates = gates fix_order_of_outputs(gates, ngates) bad_outputs: byte*[10] From 2ad98687fa090bd9c5c8756bd75d5a870986ec57 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sun, 5 Jan 2025 11:07:00 +0200 Subject: [PATCH 12/12] fix file name --- examples/aoc2024/day24/part2.jou | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/aoc2024/day24/part2.jou b/examples/aoc2024/day24/part2.jou index 16956c0c..386dd32f 100644 --- a/examples/aoc2024/day24/part2.jou +++ b/examples/aoc2024/day24/part2.jou @@ -331,7 +331,7 @@ def main() -> int: test_counting() ngates: int - gates = read_gates("input", &ngates) + gates = read_gates("sampleinput2-mixed.txt", &ngates) orig_gates = gates fix_order_of_outputs(gates, ngates)