diff --git a/pom.xml b/pom.xml index 360800a..b9410d4 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,12 @@ commons-math3 3.6.1 + + + com.google.guava + guava + 33.4.0-jre + org.jetbrains annotations diff --git a/src/main/java/com/adventofcode/flashk/common/Input.java b/src/main/java/com/adventofcode/flashk/common/Input.java new file mode 100644 index 0000000..6db5a24 --- /dev/null +++ b/src/main/java/com/adventofcode/flashk/common/Input.java @@ -0,0 +1,34 @@ +package com.adventofcode.flashk.common; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public final class Input { + + private static final String PATH_INPUTS = "src/main/resources"; + + private Input() { + } + + public static List readStringLines(String inputFolder, String inputFile) { + + List input; + + try { + Path path = Paths.get(PATH_INPUTS, inputFolder, inputFile).toAbsolutePath(); + input = Files.lines(path).collect(Collectors.toList()); + + } catch (IOException e) { + input = new ArrayList<>(); + e.printStackTrace(); + } + + return input; + } + +} diff --git a/src/main/java/com/adventofcode/flashk/common/jgrapht/LabeledEdge.java b/src/main/java/com/adventofcode/flashk/common/jgrapht/LabeledEdge.java new file mode 100644 index 0000000..45d7f6b --- /dev/null +++ b/src/main/java/com/adventofcode/flashk/common/jgrapht/LabeledEdge.java @@ -0,0 +1,34 @@ +package com.adventofcode.flashk.common.jgrapht; + +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.nio.Attribute; +import org.jgrapht.nio.AttributeType; + +/// Labeled edge class based on [JGraphT LabeledEdges](https://jgrapht.org/guide/LabeledEdges) +/// +/// Allows creating an edge with label. +/// +/// It also implements the JGraphT Attribute interface to allow exporting the labels in DOT representation +public class LabeledEdge extends DefaultEdge implements Attribute { + + private final String label; + + public LabeledEdge(String label) { + this.label = label; + } + + @Override + public String getValue() { + return label; + } + + @Override + public AttributeType getType() { + return AttributeType.STRING; + } + + @Override + public String toString() { + return "(" + getSource() + " : " + getTarget() + " : " + label + ")"; + } +} diff --git a/src/main/java/com/adventofcode/flashk/day21/redesign/Key.java b/src/main/java/com/adventofcode/flashk/day21/redesign/Key.java new file mode 100644 index 0000000..dc915ec --- /dev/null +++ b/src/main/java/com/adventofcode/flashk/day21/redesign/Key.java @@ -0,0 +1,41 @@ +package com.adventofcode.flashk.day21.redesign; + + +import lombok.Getter; + +public enum Key { + + NUMBER_0("0"), + NUMBER_1("1"), + NUMBER_2("2"), + NUMBER_3("3"), + NUMBER_4("4"), + NUMBER_5("5"), + NUMBER_6("6"), + NUMBER_7("7"), + NUMBER_8("8"), + NUMBER_9("9"), + ARROW_LEFT("<"), + ARROW_DOWN("v"), + ARROW_UP("^"), + ARROW_RIGHT(">"), + ACCEPT("A"); + + @Getter + private final String value; + + Key(String value) { + this.value = value; + } + + /* + public static Key getKey(String value) { + for(Key key : values()) { + if(key.getValue().equals(value)) { + return key; + } + } + throw new IllegalArgumentException("Unknown enum value for: "+value); + }*/ + +} diff --git a/src/main/java/com/adventofcode/flashk/day21/redesign/Keypad.java b/src/main/java/com/adventofcode/flashk/day21/redesign/Keypad.java new file mode 100644 index 0000000..9d2e4d6 --- /dev/null +++ b/src/main/java/com/adventofcode/flashk/day21/redesign/Keypad.java @@ -0,0 +1,152 @@ +package com.adventofcode.flashk.day21.redesign; + +import com.adventofcode.flashk.common.Input; +import com.adventofcode.flashk.common.jgrapht.LabeledEdge; +import com.adventofcode.flashk.day25.Key; +import com.google.common.collect.Lists; +import org.apache.commons.lang3.StringUtils; +import org.jgrapht.Graph; +import org.jgrapht.GraphPath; +import org.jgrapht.alg.shortestpath.AllDirectedPaths; +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.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class Keypad { + + private static final String FILE_DIRECTIONAL_MAPPINGS = "directional_mappings.txt"; + private static final String FILE_NUMPAD_MAPPINGS = "numpad_mappings.txt"; + + private final Graph graph = new DirectedMultigraph<>(LabeledEdge.class); + private final boolean directional; + private static final Map> memo = new HashMap<>(); + + // TODO opción 2: caché a nivel de code en lugar de button + + private String currentButton = "A"; + + + public Keypad(boolean isDirectional) { + + // Create the graph using mappings files + + // Each line of the mappings files is a comma delimited list which represents: + // source_vertex,edge_label,target_vertex + + // This allows to create a complete graph based on the contents of the graph. + directional = isDirectional; + + // Read the key mappings from the file + String filename = directional ? FILE_DIRECTIONAL_MAPPINGS : FILE_NUMPAD_MAPPINGS; + List lines = Input.readStringLines("day21", filename); + + // Create the vertexes and edges described at each line + for(String line : lines) { + + String[] parts = line.split(","); + String vertexSource = parts[0]; + String edgeLabel = parts[1]; + String vertexTarget = parts[2]; + + if(!graph.containsVertex(vertexSource)) { + graph.addVertex(vertexSource); + } + + if(!graph.containsVertex(vertexTarget)) { + graph.addVertex(vertexTarget); + } + + graph.addEdge(vertexSource, vertexTarget, new LabeledEdge(edgeLabel)); + + } + + } + + /// Enters a code to be pressed at the pad returning a [String] representing the needed moves for it. + /// + /// The code can be either an alphanumeric value as such as `029A` to be pressed at a numeric pad or an + /// alphanumeric value such as `^AvvvA` to be pressed at a directional keypad. + /// + /// @param code the code to enter. + /// @return A list of possible instructions to be executed by the next robot + public Set press(String code) { + char[] buttons = code.toCharArray(); + + List> allButtonPaths = new ArrayList<>(); + for(char button : buttons) { + + if(directional) { + button = switch(button) { + case '<' -> 'L'; + case '>' -> 'R'; + case '^' -> 'U'; + case 'v' -> 'D'; + case 'A' -> 'A'; + default -> throw new IllegalStateException("Unexpected button value: " + button); + }; + } + + // Generate a memoization status for this key press + KeypadPress state = new KeypadPress(currentButton, button); + + // Obtain from memo or compute. Add it to memoization if it didn't exist. + List buttonPaths = memo.getOrDefault(state, press(button)); + memo.putIfAbsent(state, buttonPaths); + + // + allButtonPaths.add(buttonPaths); + } + + // Create all the possible path combinations + List> cartesianList = Lists.cartesianProduct(allButtonPaths); + Set paths = new HashSet<>(); + for(List possiblePaths : cartesianList) { + paths.add(String.join(StringUtils.EMPTY, possiblePaths)); + } + + return paths; + } + + /// Presses the specified button retrieving a list of minimum cost directional key presses to achieve that key press. + /// + /// @param button the button to press. + /// @return a list of different paths as String to achieve that key press from the current button position. + public List press(char button) { + AllDirectedPaths adp = new AllDirectedPaths<>(graph); + + List> graphPaths = adp.getAllPaths(currentButton, String.valueOf(button), + true,4); + + int shortestPathSize = Integer.MAX_VALUE; + + List paths = new ArrayList<>(); + for(GraphPath graphPath : graphPaths) { + String path = graphPath.getEdgeList().stream().map(LabeledEdge::getValue).collect(Collectors.joining()) + "A"; + shortestPathSize = Math.min(path.length(), shortestPathSize); + paths.add(path); + } + + this.currentButton = String.valueOf(button); + int finalShortestPathSize = shortestPathSize; + return paths.stream().filter(p -> p.length() == finalShortestPathSize).toList(); + } + + public void paint() { + + DOTExporter exporter = new DOTExporter<>(v -> v); + exporter.setEdgeAttributeProvider(e -> Map.of("label", e)); + + Writer writer = new StringWriter(); + exporter.exportGraph(graph, writer); + System.out.println(writer.toString()); + } +} diff --git a/src/main/java/com/adventofcode/flashk/day21/redesign/KeypadConundrum2.java b/src/main/java/com/adventofcode/flashk/day21/redesign/KeypadConundrum2.java new file mode 100644 index 0000000..9ffb424 --- /dev/null +++ b/src/main/java/com/adventofcode/flashk/day21/redesign/KeypadConundrum2.java @@ -0,0 +1,63 @@ +package com.adventofcode.flashk.day21.redesign; + + + +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.Set; + +public class KeypadConundrum2 { + + private List codes; + + private Deque keypads = new ArrayDeque<>(); + + public KeypadConundrum2(List inputs, int keypadsNumber) { + codes = inputs; + keypads.add(new Keypad(false)); + for(int i = 0; i < keypadsNumber; i++) { + keypads.add(new Keypad(true)); + } + } + + public long solveA() { + + long result = 0; + for(String code : codes) { + result += press(code); + } + + return result; + } + + private long press(String code) { + long shortestSequenceLength = pressCode(code); + long numericValue = Long.parseLong(code.replace("A", StringUtils.EMPTY)); + return shortestSequenceLength * numericValue; + } + + private long pressCode(String code) { + + if(keypads.isEmpty()) { + return code.length(); + } + + long shortestSequence = Long.MAX_VALUE; + + Keypad nextKeypad = keypads.pollFirst(); + + Set keyPressesList = nextKeypad.press(code); + for(String keyPresses : keyPressesList) { + long sequence = pressCode(keyPresses); + shortestSequence = Math.min(shortestSequence, sequence); + } + + keypads.addFirst(nextKeypad); + + return shortestSequence; + } + +} diff --git a/src/main/java/com/adventofcode/flashk/day21/redesign/KeypadPress.java b/src/main/java/com/adventofcode/flashk/day21/redesign/KeypadPress.java new file mode 100644 index 0000000..eca073b --- /dev/null +++ b/src/main/java/com/adventofcode/flashk/day21/redesign/KeypadPress.java @@ -0,0 +1,4 @@ +package com.adventofcode.flashk.day21.redesign; + +public record KeypadPress(String currentButton, char nextButton) { +} diff --git a/src/main/resources/day21/directional_mappings.txt b/src/main/resources/day21/directional_mappings.txt new file mode 100644 index 0000000..10271eb --- /dev/null +++ b/src/main/resources/day21/directional_mappings.txt @@ -0,0 +1,10 @@ +U,v,D +U,>,A +A,<,U +A,v,R +L,>,D +D,<,L +D,^,U +D,>,R +R,<,D +R,^,A \ No newline at end of file diff --git a/src/main/resources/day21/numpad_mappings.txt b/src/main/resources/day21/numpad_mappings.txt new file mode 100644 index 0000000..24ca858 --- /dev/null +++ b/src/main/resources/day21/numpad_mappings.txt @@ -0,0 +1,30 @@ +7,>,8 +7,v,4 +8,<,7 +8,>,9 +8,v,5 +9,<,8 +9,v,6 +4,^,7 +4,>,5 +4,v,1 +5,<,4 +5,^,8 +5,>,6 +5,v,2 +6,<,5 +6,^,9 +6,v,3 +1,>,2 +1,^,4 +2,<,1 +2,^,5 +2,>,3 +2,v,0 +3,<,2 +3,^,6 +3,v,A +0,>,A +0,^,2 +A,<,0 +A,^,3 \ No newline at end of file diff --git a/src/test/java/com/adventofcode/flashk/day21/Day21Test.java b/src/test/java/com/adventofcode/flashk/day21/Day21Test.java index 0bfe5ce..c2fbc89 100644 --- a/src/test/java/com/adventofcode/flashk/day21/Day21Test.java +++ b/src/test/java/com/adventofcode/flashk/day21/Day21Test.java @@ -2,6 +2,7 @@ import java.util.List; +import com.adventofcode.flashk.day21.redesign.KeypadConundrum2; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; @@ -22,7 +23,7 @@ @DisplayName(TestDisplayName.DAY_21) @TestMethodOrder(OrderAnnotation.class) @Disabled("[Disabled] Day 21 - Work in Progress") -public class Day21Test extends PuzzleTest { +public class Day21Test { private static final String INPUT_FOLDER = TestFolder.DAY_21; @@ -36,7 +37,7 @@ public void testSolvePart1Sample() { // Read input file List inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE_SAMPLE); - KeypadConundrum keypadConundrum = new KeypadConundrum(inputs); + KeypadConundrum2 keypadConundrum = new KeypadConundrum2(inputs, 2); assertEquals(126384L, keypadConundrum.solveA()); //assertEquals(0L,keypadConundrum.solveA()); @@ -52,23 +53,10 @@ public void testSolvePart1Input() { // Read input file List inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE); - KeypadConundrum keypadConundrum = new KeypadConundrum(inputs); - System.out.println("Solution: "+keypadConundrum.solveA()); - assertEquals(0L,0L); - - } + KeypadConundrum2 keypadConundrum = new KeypadConundrum2(inputs, 2); - @Test - @Order(3) - @Tag(TestTag.PART_2) - @Tag(TestTag.SAMPLE) - @DisplayName(TestDisplayName.PART_2_SAMPLE) - public void testSolvePart2Sample() { + assertEquals(206798L,keypadConundrum.solveA()); - // Read input file - List inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE_SAMPLE); - - assertEquals(0L,0L); } @Test @@ -81,7 +69,9 @@ public void testSolvePart2Input() { // Read input file List inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE); - System.out.println("Solution: "); + KeypadConundrum2 keypadConundrum = new KeypadConundrum2(inputs, 25); + + System.out.println("Solution: "+ keypadConundrum.solveA()); assertEquals(0L,0L); } diff --git a/src/test/java/com/adventofcode/flashk/day21/KeyTest.java b/src/test/java/com/adventofcode/flashk/day21/KeyTest.java new file mode 100644 index 0000000..746fbaa --- /dev/null +++ b/src/test/java/com/adventofcode/flashk/day21/KeyTest.java @@ -0,0 +1,16 @@ +package com.adventofcode.flashk.day21; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class KeyTest { + + @Test + void values() { + } + + @Test + void valueOf() { + } +} \ No newline at end of file