From f59db64587c38d10b96ed9485000290569bfc374 Mon Sep 17 00:00:00 2001 From: Christopher Bohn <bohn@unl.edu> Date: Thu, 23 Dec 2021 15:40:15 -0600 Subject: [PATCH] Day 16 Part 1 complete --- 2021/README.md | 37 +-- .../java/edu/unl/cse/bohn/year2021/Day15.java | 217 ++++++++++++++++++ .../java/edu/unl/cse/bohn/year2021/Day16.java | 44 ++++ .../cse/bohn/year2021/computer/Computer.java | 34 +++ .../year2021/computer/LiteralValuePacket.java | 25 ++ .../year2021/computer/OperatorPacket.java | 33 +++ .../cse/bohn/year2021/computer/Packet.java | 42 ++++ 7 files changed, 416 insertions(+), 16 deletions(-) create mode 100644 2021/src/main/java/edu/unl/cse/bohn/year2021/Day15.java create mode 100644 2021/src/main/java/edu/unl/cse/bohn/year2021/Day16.java create mode 100644 2021/src/main/java/edu/unl/cse/bohn/year2021/computer/Computer.java create mode 100644 2021/src/main/java/edu/unl/cse/bohn/year2021/computer/LiteralValuePacket.java create mode 100644 2021/src/main/java/edu/unl/cse/bohn/year2021/computer/OperatorPacket.java create mode 100644 2021/src/main/java/edu/unl/cse/bohn/year2021/computer/Packet.java diff --git a/2021/README.md b/2021/README.md index c38c2a6..3ac0c81 100644 --- a/2021/README.md +++ b/2021/README.md @@ -177,11 +177,8 @@ on Day 6, no big deal. That, and waiting a bit longer for the answer. Quite a bi longer, it seems. Is there any place I can optimize? I sure hope so, since I just hit an OutOfMemoryError on the *sample data*! -I don't see how I can avoid modeling the full string. - Maybe I can have the StringBuilder work on smaller chunks, thereby allowing for -more frequent garbage collection? That probably will work. I'm pretty sure that -StringBuilder has to have an underlying linked list. +more frequent garbage collection? That doesn't seem to be speeding things up, but hopefully it'll help with the memory problem. (Two hours later...) memory didn't blow up, but the sample data @@ -194,21 +191,29 @@ in a few seconds we got past the point that the StringBuilder approach took a couple of hours while I was in a meeting. But we still ran out of memory when growing the new polymer on iteration 28 of the sample data. -Perhaps instead of creating strings, we can keep track of the positions of each -element in the polymer. Computationally, this will increase the asymptotic -complexity, probably by two orders of magnitude, but asymptotic complexity isn't -everything: memory management was definitely a HUGE constant factor. Keeping -track of the positions of each element in the polymer will use very, very little -memory. - -Yup, this has definitely slowed down computation. And, now that I think about -it, I have to create as many indices as the original string's length was. This -isn't a savings. At. All. - What am I missing? Let's try it this way: I don't need to keep track of the molecule *at all*. If I keep track of the number of occurrences of each pair, then the numbers of occurrences will grow and shrink in each iteration. -Yup. That did it. +Yup. That did it. Note to self: if the problem space doubles every iteration, +look for a different solution space. + +## Day 15 + +This looks like a job for Dijkstra's Algorithm, or a variation thereon. Here the +weights are on the nodes instead of the edges :shrug: -- but with this being +an undirected graph, there might be a problem. (*Is* it an undirected graph? +The path in the sample data always increases x or y, never decreases.) + +Part 1 appears not to have been an undirected graph, since I got the correct +answer assuming x & y are (non-strictly) monotonically increasing. For Part 2, +is there any way I can take advantage of the tiling? I suppose I'll start by +simply creating a larger graph -- that's not really taking advantage of the +relationships between the tiles, though. + +## Day 16 + +Part 1 looks to be about preparing this year's computer's syntax tree. I think +I'll try out Java's new "sealed class." \ No newline at end of file diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day15.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day15.java new file mode 100644 index 0000000..2ff4d2c --- /dev/null +++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day15.java @@ -0,0 +1,217 @@ +package edu.unl.cse.bohn.year2021; + +import edu.unl.cse.bohn.Puzzle; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.PriorityQueue; +import java.util.Set; + +@SuppressWarnings("unused") +public class Day15 extends Puzzle { + private PriorityQueue<Node> unvisitedNodes; + + @SuppressWarnings("FieldCanBeLocal") + private final String newCave = """ + 11637517422274862853338597396444961841755517295286 + 13813736722492484783351359589446246169155735727126 + 21365113283247622439435873354154698446526571955763 + 36949315694715142671582625378269373648937148475914 + 74634171118574528222968563933317967414442817852555 + 13191281372421239248353234135946434524615754563572 + 13599124212461123532357223464346833457545794456865 + 31254216394236532741534764385264587549637569865174 + 12931385212314249632342535174345364628545647573965 + 23119445813422155692453326671356443778246755488935 + 22748628533385973964449618417555172952866628316397 + 24924847833513595894462461691557357271266846838237 + 32476224394358733541546984465265719557637682166874 + 47151426715826253782693736489371484759148259586125 + 85745282229685639333179674144428178525553928963666 + 24212392483532341359464345246157545635726865674683 + 24611235323572234643468334575457944568656815567976 + 42365327415347643852645875496375698651748671976285 + 23142496323425351743453646285456475739656758684176 + 34221556924533266713564437782467554889357866599146 + 33859739644496184175551729528666283163977739427418 + 35135958944624616915573572712668468382377957949348 + 43587335415469844652657195576376821668748793277985 + 58262537826937364893714847591482595861259361697236 + 96856393331796741444281785255539289636664139174777 + 35323413594643452461575456357268656746837976785794 + 35722346434683345754579445686568155679767926678187 + 53476438526458754963756986517486719762859782187396 + 34253517434536462854564757396567586841767869795287 + 45332667135644377824675548893578665991468977611257 + 44961841755517295286662831639777394274188841538529 + 46246169155735727126684683823779579493488168151459 + 54698446526571955763768216687487932779859814388196 + 69373648937148475914825958612593616972361472718347 + 17967414442817852555392896366641391747775241285888 + 46434524615754563572686567468379767857948187896815 + 46833457545794456865681556797679266781878137789298 + 64587549637569865174867197628597821873961893298417 + 45364628545647573965675868417678697952878971816398 + 56443778246755488935786659914689776112579188722368 + 55172952866628316397773942741888415385299952649631 + 57357271266846838237795794934881681514599279262561 + 65719557637682166874879327798598143881961925499217 + 71484759148259586125936169723614727183472583829458 + 28178525553928963666413917477752412858886352396999 + 57545635726865674683797678579481878968159298917926 + 57944568656815567976792667818781377892989248891319 + 75698651748671976285978218739618932984172914319528 + 56475739656758684176786979528789718163989182927419 + 67554889357866599146897761125791887223681299833479"""; + + public Day15(boolean isProductionReady) { + super(isProductionReady); + sampleData = """ + 1163751742 + 1381373672 + 2136511328 + 3694931569 + 7463417111 + 1319128137 + 1359912421 + 3125421639 + 1293138521 + 2311944581"""; + } + + private void initialize(List<String> data) { + Node.createNodes(data); + unvisitedNodes = new PriorityQueue<>(data.size() * data.get(0).length(), + Comparator.comparingLong(Node::getTotalWeight)); + Node initialPoint = Node.nodes.stream().filter(n -> n.x == 0 && n.y == 0).findAny().orElseThrow(); + initialPoint.offerNewPath(0); + unvisitedNodes.addAll(Node.nodes); + } + + private void findLeastRiskyPath(Node destination) { + Node node; + do { + node = unvisitedNodes.remove(); +// Node effectivelyFinalNode = node; +// List<Node> neighbors = Node.nodes.stream() +// .filter(n -> (n.x == effectivelyFinalNode.x && n.y - effectivelyFinalNode.y == 1) || +// (n.x - effectivelyFinalNode.x == 1 && n.y == effectivelyFinalNode.y)) +// .toList(); + Node[] neighbors = {Node.getNodeAt(node.x, node.y+1), Node.getNodeAt(node.x+1, node.y)}; + for (Node neighbor : neighbors) { + if (neighbor != null && unvisitedNodes.contains(neighbor)) { + unvisitedNodes.remove(neighbor); + neighbor.offerNewPath(node.getTotalWeight()); + unvisitedNodes.add(neighbor); + } + } + } while (node != destination); + } + + @SuppressWarnings("DuplicatedCode") + @Override + public long computePart1(List<String> data) { + initialize(data); + Node initialPoint = Node.getNodeAt(0,0); + Node destination = Node.getNodeAt(data.size() - 1, data.get(0).length() - 1); + findLeastRiskyPath(destination); + return destination.getTotalWeight() - initialPoint.getTotalWeight(); + } + + private List<String> createTile(List<String> originalTile, int modifier) { + List<String> newTile = new ArrayList<>(originalTile.size()); + for (String row : originalTile) { + StringBuilder stringBuilder = new StringBuilder(); + for (char risk : row.toCharArray()) { + int originalRisk = Integer.parseInt(String.valueOf(risk)); + int newRisk = (originalRisk + modifier) % 10; + stringBuilder.append(newRisk); + } + newTile.add(stringBuilder.toString()); + } + return newTile; + } + + private List<String> growData(List<String> data) { + List<String> newData = new ArrayList<>(5 * data.size()); + for (int i = 0; i < 5; i++) { + List<List<String>> tiles = new ArrayList<>(5); + for (int j = 0; j < 5; j++) { + tiles.add(createTile(data, i + j)); + } + for (int k = 0; k < data.size(); k++) { + newData.add(tiles.get(0).get(k) + tiles.get(1).get(k) + + tiles.get(2).get(k) + tiles.get(3).get(k) + tiles.get(4).get(k)); + } + } + List<String> expectedData = List.of(newCave.split(System.lineSeparator())); + assert newData.size() == expectedData.size(); + for (int i = 0; i < newData.size(); i++) { + assert newData.get(i).equals(expectedData.get(i)); + } + return newData; + } + + @SuppressWarnings("DuplicatedCode") + @Override + public long computePart2(List<String> data) { + List<String> largerData = growData(data); + initialize(largerData); + Node initialPoint = Node.getNodeAt(0,0); + Node destination = Node.getNodeAt(largerData.size() - 1, largerData.get(0).length() - 1); + findLeastRiskyPath(destination); + return destination.getTotalWeight() - initialPoint.getTotalWeight(); + } + + private static class Node { + public static Set<Node> nodes; + + public final int x; + public final int y; + public final long weight; + private long weightToGetHere; + + private Node(int x, int y, long weight) { + this.x = x; + this.y = y; + this.weight = weight; + weightToGetHere = Long.MAX_VALUE; + } + + public static void createNodes(List<String> data) { + int width = data.size(); + int height = data.get(0).length(); + Set<Node> workingSet = new HashSet<>(); + for (int x = 0; x < width; x++) { + String row = data.get(x); + for (int y = 0; y < height; y++) { + long weight = Long.parseLong(row.substring(y, y + 1)); + Node node = new Node(x, y, weight); + workingSet.add(node); + } + } + nodes = Set.copyOf(workingSet); + } + + public static Node getNodeAt(int x, int y) { + return nodes.stream().filter(node -> node.x == x && node.y == y).findAny().orElse(null); + } + + // returns TRUE iff the offered path is shorter + @SuppressWarnings("UnusedReturnValue") + public boolean offerNewPath(long possiblyShorterWeightToGetHere) { + if (possiblyShorterWeightToGetHere < weightToGetHere) { + weightToGetHere = possiblyShorterWeightToGetHere; + return true; + } else { + return false; + } + } + + public long getTotalWeight() { + return weightToGetHere == Long.MAX_VALUE ? weightToGetHere : weight + weightToGetHere; + } + } +} diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day16.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day16.java new file mode 100644 index 0000000..8a15b29 --- /dev/null +++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day16.java @@ -0,0 +1,44 @@ +package edu.unl.cse.bohn.year2021; + +import edu.unl.cse.bohn.Puzzle; +import edu.unl.cse.bohn.year2021.computer.Computer; + +import java.util.List; + +@SuppressWarnings("unused") +public class Day16 extends Puzzle { + @SuppressWarnings("CommentedOutCode") + public Day16(boolean isProductionReady) { + super(isProductionReady); + // a single packet at its outermost layer which itself contains many other packets + // "The hexadecimal representation of this packet might encode a few extra 0 bits at the end; + // these are not part of the transmission and should be ignored." + // + // The first three bits encode the packet version (a number) + // The next 3 bits encode the packet type ID (a number) + // + // Type 4: literal value + // - Pad with leading 0s until length is multiple of 4 bits, then broken into groups of 4 bits + // - Each group prefixed by 1 except last group which is prefixed by 0 (i.e., there are 5 bits per 4-bit group) + // + // Type not-4: operator on one or more subpackets +// sampleData = "D2FE28"; // literal value packet +// sampleData = "38006F45291200"; // operator packet with length-based subpackets +// sampleData = "EE00D40C823060"; // operator packet with cardinality-based subpackets +// sampleData = "8A004A801A8002F478"; // version sum = 16 +// sampleData = "620080001611562C8802118E34"; // version sum = 12 +// sampleData = "C0015000016115A2E0802F182340"; // version sum = 23 + sampleData = "A0016C880162017C3686B18A3D4780"; // version sum = 31 + } + + @Override + public long computePart1(List<String> data) { + Computer computer = new Computer(data.get(0)); + return computer.addPacketVersionNumbers(); + } + + @Override + public long computePart2(List<String> data) { + return 0; + } +} diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/computer/Computer.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/computer/Computer.java new file mode 100644 index 0000000..b413c65 --- /dev/null +++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/computer/Computer.java @@ -0,0 +1,34 @@ +package edu.unl.cse.bohn.year2021.computer; + +import java.util.BitSet; + +public class Computer { + private BitSet binaryPacket; + private int numberOfBits; + private final Packet syntaxTree; + + public Computer(String hexPacket) { + bitifyPacket(hexPacket); + syntaxTree = Packet.parse(binaryPacket, numberOfBits - 1, 0); + } + + private void bitifyPacket(String hexPacket) { + int numberOfNibbles = hexPacket.length(); + int numberOfBytes = (int)Math.ceil(numberOfNibbles / 2.0); + byte[] bytes = new byte[numberOfBytes]; + int nibbleCounter = 0; + while (nibbleCounter < numberOfNibbles) { + byte nextByte = (byte)(Byte.parseByte(String.valueOf(hexPacket.charAt(nibbleCounter++)), 16) << 4); + if (nibbleCounter < numberOfNibbles) { + nextByte |= Byte.parseByte(String.valueOf(hexPacket.charAt(nibbleCounter++)), 16); + } + bytes[numberOfBytes - nibbleCounter / 2] = nextByte; + } + numberOfBits = numberOfBytes * 8; + binaryPacket = BitSet.valueOf(bytes); + } + + public long addPacketVersionNumbers() { + return syntaxTree.addPacketVersionNumbers(); + } +} diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/computer/LiteralValuePacket.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/computer/LiteralValuePacket.java new file mode 100644 index 0000000..f4f2e82 --- /dev/null +++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/computer/LiteralValuePacket.java @@ -0,0 +1,25 @@ +package edu.unl.cse.bohn.year2021.computer; + +import java.util.BitSet; + +final class LiteralValuePacket extends Packet { + public final long value; + + LiteralValuePacket(BitSet bits, int upperIndex, @SuppressWarnings("unused") int lowerIndex, long version, long typeID) { + super(version, typeID); + int index = upperIndex; + long literalValue = 0; + do { + BitSet nibbleBits = bits.get(index - 4, index); + literalValue = (literalValue << 4) | (nibbleBits.isEmpty() ? 0 : nibbleBits.toByteArray()[0]); + index -= 5; + } while (bits.get(index + 5)); // terminates when the prefix is 0 + value = literalValue; + newUpperIndex = index; + } + + @Override + public long addPacketVersionNumbers() { + return version; + } +} diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/computer/OperatorPacket.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/computer/OperatorPacket.java new file mode 100644 index 0000000..b25fb72 --- /dev/null +++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/computer/OperatorPacket.java @@ -0,0 +1,33 @@ +package edu.unl.cse.bohn.year2021.computer; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.LinkedList; +import java.util.List; + +public final class OperatorPacket extends Packet { + private final List<Packet> subPackets; + + OperatorPacket(BitSet bits, int upperIndex, int lowerIndex, int version, int typeID) { + super(version, typeID); + subPackets = new LinkedList<>(); + while (upperIndex > lowerIndex) { + subPackets.add(Packet.parse(bits, upperIndex, lowerIndex)); + upperIndex = newUpperIndex; + } + } + + OperatorPacket(BitSet bits, int upperIndex, int lowerIndex, int numberOfSubPackets, int version, int typeID) { + super(version, typeID); + subPackets = new ArrayList<>(numberOfSubPackets); + for (int i = 0; i < numberOfSubPackets; i++) { + subPackets.add(Packet.parse(bits, upperIndex, lowerIndex)); + upperIndex = newUpperIndex; + } + } + + @Override + public long addPacketVersionNumbers() { + return version + subPackets.stream().mapToLong(Packet::addPacketVersionNumbers).sum(); + } +} diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/computer/Packet.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/computer/Packet.java new file mode 100644 index 0000000..439ad42 --- /dev/null +++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/computer/Packet.java @@ -0,0 +1,42 @@ +package edu.unl.cse.bohn.year2021.computer; + +import java.util.BitSet; + +public abstract sealed class Packet permits LiteralValuePacket, OperatorPacket { + protected final long version; + protected final long typeID; + + protected static int newUpperIndex; // used to communicate between constructors + public static Packet parse(BitSet bits, int upperIndex, int lowerIndex) { + BitSet versionBits = bits.get(upperIndex - 2, upperIndex + 1); + int version = versionBits.isEmpty() ? 0 : versionBits.toByteArray()[0]; + upperIndex -= 3; + BitSet typeBits = bits.get(upperIndex - 2, upperIndex + 1); + int typeID = typeBits.isEmpty() ? 0 : typeBits.toByteArray()[0]; + upperIndex -= 3; + if (typeID == 4) { + return new LiteralValuePacket(bits, upperIndex, lowerIndex, version, typeID); + } else { + if (bits.get(upperIndex)) { + // the next 11 bits are a number that represents the number of sub-packets immediately contained by + // this packet + int numberOfSubPackets = (int)bits.get(upperIndex - 11, upperIndex).toLongArray()[0]; + upperIndex -= 12; + return new OperatorPacket(bits, upperIndex, lowerIndex, numberOfSubPackets, version, typeID); + } else { + // the next 15 bits are a number that represents the total length in bits of the sub-packets + // contained by this packet + int lengthOfSubPackets = (int)bits.get(upperIndex - 15, upperIndex).toLongArray()[0]; + upperIndex -= 16; + return new OperatorPacket(bits, upperIndex, upperIndex - lengthOfSubPackets + 1, version, typeID); + } + } + } + + protected Packet(long version, long typeID) { + this.version = version; + this.typeID = typeID; + } + + public abstract long addPacketVersionNumbers(); +} -- GitLab