diff --git a/pom.xml b/pom.xml index 7a55bd3..e9a3931 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,11 @@ jgrapht-core 1.5.2 + + org.jgrapht + jgrapht-io + 1.5.2 + com.fasterxml.jackson.core diff --git a/src/main/java/com/adventofcode/flashk/day24/CrossedWiresVisual.java b/src/main/java/com/adventofcode/flashk/day24/CrossedWiresVisual.java new file mode 100644 index 0000000..03abbc0 --- /dev/null +++ b/src/main/java/com/adventofcode/flashk/day24/CrossedWiresVisual.java @@ -0,0 +1,177 @@ +package com.adventofcode.flashk.day24; + +import org.apache.commons.lang3.StringUtils; +import org.jgrapht.Graph; +import org.jgrapht.graph.DirectedMultigraph; +import org.jgrapht.nio.dot.DOTExporter; + +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class CrossedWiresVisual { + + private static final Pattern GATE_PATTERN = Pattern.compile("(\\w*) (AND|XOR|OR) (\\w*) -> (\\w*)"); + + private final Graph graph = new DirectedMultigraph<>(String.class); + + private final Map gatesPerOutput = new HashMap<>(); + private final Map wires = new HashMap<>(); + private final List gates = new ArrayList<>(); + private List endWires; + + public CrossedWiresVisual(List inputs) { + initializeWires(inputs); + initializeGates(inputs); + createStartEndVertexes(); + connectGates(); + } + + private void connectGates() { + // Por cada puerta + // Buscar si existe en gatesPerOutput + for(GateVisual gate : gatesPerOutput.values()) { + String output = gate.getOutput(); + + // Search gates that have this output gate as input 1. + List gatesInput1 = gates.stream().filter(g -> output.equals(g.getInput1())).toList(); + for(GateVisual gateInput1 : gatesInput1) { + String edgeId = output + "_" + UUID.randomUUID(); + graph.addEdge(gate, gateInput1, edgeId); + } + + // Search gates that have this output gate as input 2. + List gatesInput2 = gates.stream().filter(g -> output.equals(g.getInput2())).toList(); + for(GateVisual gateInput2 : gatesInput2) { + String edgeId = output + "_" + UUID.randomUUID(); + graph.addEdge(gate, gateInput2, edgeId); + } + + } + + } + + private void createStartEndVertexes() { + wires.keySet().stream().filter(this::isStartOrEnd).forEach(this::createStartEndVertex); + } + + + private void initializeGates(List inputs) { + List gatesInputs = inputs.stream().skip(inputs.indexOf(StringUtils.EMPTY)+1).toList(); + + for(String input : gatesInputs) { + Matcher matcher = GATE_PATTERN.matcher(input); + if(matcher.find()) { + String operation = matcher.group(2); + String input1 = matcher.group(1); + String input2 = matcher.group(3); + String output = matcher.group(4); + + GateVisual gateVisual = new GateVisual(operation, input1, input2, output); + gates.add(gateVisual); + gatesPerOutput.put(output, gateVisual); + graph.addVertex(gateVisual); + } + } + } + + private void initializeWires(List inputs) { + + boolean inputGates = false; + for (String input : inputs) { + + if (StringUtils.EMPTY.equals(input)) { + inputGates = true; + continue; + } + + if (!inputGates) { + String[] wireInput = input.replace(StringUtils.SPACE, StringUtils.EMPTY).split(":"); + wires.put(wireInput[0], Integer.parseInt(wireInput[1])); + } else { + Matcher matcher = GATE_PATTERN.matcher(input); + if (matcher.find()) { + wires.putIfAbsent(matcher.group(1), -1); + wires.putIfAbsent(matcher.group(3), -1); + wires.putIfAbsent(matcher.group(4), -1); + } + } + } + + endWires = wires.keySet().stream().filter(w -> w.startsWith("z")).sorted().toList().reversed(); + } + + + public String solveB() { + + // Obtain the graph in DOT format: + paint(); + + // Visual finding of invalid edges + System.out.println("Tools:"); + System.out.println("- (Optional) graphviz: sudo apt install graphviz"); + System.out.println("- graphviz2drawio: pip install graphviz2drawio"); + System.out.println(); + System.out.println("Procedure:"); + System.out.println("1. Add the result to a file: dotgraph_day24.txt"); + System.out.println("2. (Optional) Execute command: cat day24.txt | dot -Tsvg > day24.svg"); + System.out.println("3. Execute command: graphviz2drawio dotgraph_day24.txt"); + System.out.println("4. Open the exported 'dotgraph_day24.xml' file at https://draw.io"); + System.out.println("5. Order the graph nodes until you see the invalid connections"); + + // Invalid gates are: + // bbp AND wwg -> z09 + // wwg XOR bbp -> hnd + // ncj OR pwk -> z16 + // jkw XOR mqf -> tdv + // x23 AND y23 -> z23 + // mfr XOR scw -> bks + // x37 AND y37 -> tjp + // x37 XOR y37 -> nrn + + // So the outputs are: z09,hnd,z16,tdv,z23,bks,tjp,nrn + List outputGates = List.of("z09","hnd","z16","tdv","z23","bks","tjp","nrn"); + + return outputGates.stream().sorted().collect(Collectors.joining(",")); + } + + private void createStartEndVertex(String wire) { + GateVisual gateVisual = new GateVisual(wire); + graph.addVertex(gateVisual); + + if(isStart(wire)) { + gatesPerOutput.put(wire, gateVisual); + } else if(isEnd(wire)) { + gates.add(gateVisual); + } + + } + + private boolean isStart(String wire) { + return wire.startsWith("x") || wire.startsWith("y"); + } + + private boolean isEnd(String wire) { + return wire.startsWith("z"); + } + + private boolean isStartOrEnd(String wire) { + return isStart(wire) || isEnd(wire); + } + + private void paint() { + DOTExporter exporter = new DOTExporter<>(v -> v.getLabel()); + Writer writer = new StringWriter(); + exporter.exportGraph(graph, writer); + System.out.println(writer.toString()); + } + + +} diff --git a/src/main/java/com/adventofcode/flashk/day24/GateVisual.java b/src/main/java/com/adventofcode/flashk/day24/GateVisual.java new file mode 100644 index 0000000..cc51bb4 --- /dev/null +++ b/src/main/java/com/adventofcode/flashk/day24/GateVisual.java @@ -0,0 +1,44 @@ +package com.adventofcode.flashk.day24; + +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +@Getter +public class GateVisual { + + private static int id = 0; + + private final String label; + private final String operation; + private String input1; + private String input2; + private String output; + + public GateVisual(String label) { + this.label = label; + this.operation = StringUtils.EMPTY; + if(label.startsWith("x") || label.startsWith("y")) { + this.output = label; + } else if(label.startsWith("z")) { + this.input1 = label; + } + } + + public GateVisual(String operation, String input1, String input2, String output) { + this.label = operation + "_" + output; + this.operation = operation; + this.input1 = input1; + this.input2 = input2; + this.output = output; + } + + public int operate(int value1, int value2) { + return switch (operation) { + case "AND" -> value1 & value2; + case "XOR" -> value1 ^ value2; + case "OR" -> value1 | value2; + case null, default -> throw new IllegalStateException("Unknown gate operation: " + operation); + }; + } + +} diff --git a/src/test/java/com/adventofcode/flashk/common/test/constants/TestFilename.java b/src/test/java/com/adventofcode/flashk/common/test/constants/TestFilename.java index 06f010e..fbfd10f 100644 --- a/src/test/java/com/adventofcode/flashk/common/test/constants/TestFilename.java +++ b/src/test/java/com/adventofcode/flashk/common/test/constants/TestFilename.java @@ -8,6 +8,7 @@ private TestFilename() {} public static final String INPUT_FILE = "data.input"; public static final String INPUT_FILE_SAMPLE = "sample.input"; public static final String INPUT_FILE_SAMPLE_2 = "sample_2.input"; + public static final String INPUT_FILE_SAMPLE_3 = "sample_3.input"; public static final String INPUT_FILE_SAMPLE_PART_2 = "sample_part_2.input"; public static final String INPUT_FILE_SINGLE_SAMPLE = "single_sample.input"; public static final String INPUT_FILE_SINGLE_SAMPLE_2 = "single_sample_2.input"; diff --git a/src/test/java/com/adventofcode/flashk/day24/Day24Test.java b/src/test/java/com/adventofcode/flashk/day24/Day24Test.java index a85b890..01838f7 100644 --- a/src/test/java/com/adventofcode/flashk/day24/Day24Test.java +++ b/src/test/java/com/adventofcode/flashk/day24/Day24Test.java @@ -14,15 +14,13 @@ import com.adventofcode.flashk.common.test.constants.TestFilename; import com.adventofcode.flashk.common.test.constants.TestFolder; import com.adventofcode.flashk.common.test.constants.TestTag; -import com.adventofcode.flashk.common.test.utils.PuzzleTest; import com.adventofcode.flashk.common.test.utils.Input; import static org.junit.jupiter.api.Assertions.assertEquals; @DisplayName(TestDisplayName.DAY_24) @TestMethodOrder(OrderAnnotation.class) -@Disabled // TODO Remove comment when implemented -public class Day24Test extends PuzzleTest { +class Day24Test { private static final String INPUT_FOLDER = TestFolder.DAY_24; @@ -31,7 +29,7 @@ public class Day24Test extends PuzzleTest { @Tag(TestTag.PART_1) @Tag(TestTag.SAMPLE) @DisplayName(TestDisplayName.PART_1_SAMPLE) - public void testSolvePart1Sample() { + void part1SampleTest() { // Read input file List inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE_SAMPLE); @@ -47,7 +45,7 @@ public void testSolvePart1Sample() { @Tag(TestTag.PART_1) @Tag(TestTag.SAMPLE) @DisplayName(TestDisplayName.PART_1_SAMPLE_2) - public void testSolvePart1Sample2() { + void part1Sample2Test() { // Read input file List inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE_SAMPLE_2); @@ -62,7 +60,7 @@ public void testSolvePart1Sample2() { @Tag(TestTag.PART_1) @Tag(TestTag.INPUT) @DisplayName(TestDisplayName.PART_1_INPUT) - public void testSolvePart1Input() { + void part1InputTest() { // Read input file List inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE); @@ -78,11 +76,28 @@ public void testSolvePart1Input() { @Tag(TestTag.PART_2) @Tag(TestTag.SAMPLE) @DisplayName(TestDisplayName.PART_2_SAMPLE) - public void testSolvePart2Sample() { + void part2SampleTest() { // Read input file List inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE_SAMPLE); + CrossedWiresVisual crossedWires = new CrossedWiresVisual(inputs); + crossedWires.solveB(); + assertEquals(0L,0L); + } + + @Test + @Order(3) + @Tag(TestTag.PART_2) + @Tag(TestTag.SAMPLE) + @DisplayName(TestDisplayName.PART_2_SAMPLE) + void part2Sample3Test() { + + // Read input file + List inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE_SAMPLE_3); + CrossedWiresVisual crossedWires = new CrossedWiresVisual(inputs); + + crossedWires.solveB(); assertEquals(0L,0L); } @@ -91,13 +106,13 @@ public void testSolvePart2Sample() { @Tag(TestTag.PART_2) @Tag(TestTag.INPUT) @DisplayName(TestDisplayName.PART_2_INPUT) - public void testSolvePart2Input() { + void part2InputTest() { // Read input file List inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE); + CrossedWiresVisual crossedWires = new CrossedWiresVisual(inputs); - System.out.println("Solution: "); - assertEquals(0L,0L); + assertEquals("bks,hnd,nrn,tdv,tjp,z09,z16,z23", crossedWires.solveB()); }