Skip to content
Snippets Groups Projects
Commit f59db645 authored by Christopher Bohn's avatar Christopher Bohn :thinking:
Browse files

Day 16 Part 1 complete

parent ab35275f
No related branches found
No related tags found
No related merge requests found
......@@ -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
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;
}
}
}
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;
}
}
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();
}
}
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;
}
}
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();
}
}
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();
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment