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