michael-simons
: michael-simons
michael-simons |
I am a father, husband and athlete (the later is probably just wishful thinking). Apart from that, I’m also a Java Champion, Oracle Cloud Groundbreaker ambassador, published author, JUG leader and currently working on Spring Data and Neo4j-OGM at Neo4j. In this role, my main focus is providing first class support of Neo4j in the Spring Environment, but also made contributions to Testcontainers, Spring Boot, Quarkus and a lot of other projects.
This is an over engineered "Hello, World" application written in Groovy, using Spring Boot and the reactive web stack. Of course, one wouldn’t need a framework for that, but since Ralf asked for it… ¯\_(ツ)_/¯.
Day 0 doesn’t correspond to any of the official AoC puzzles.
The application defines a HelloWorldController
as a top level Spring bean:
@RestController
static class HelloWorldController {
@GetMapping("/")
def index() {
Mono.just("Hello, World")
}
}
The solutions shall print out a greeting.
I will call the above endpoint via Springs reactive WebClient
and subscribe to the flow.
This is done in a Spring Application listener, reacting on ApplicationReadyEvent
.
ApplicationReadyEvent
is fired when the application is ready to serve requests:
After the flow ends, I’ll define a completable future to shutdown the application context.
That needs to happen on a new thread, as it contains some blocking calls, which are not allowed in a reactive flow.
static class ApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
def applicationContext = applicationReadyEvent.getApplicationContext();
def port = ((WebServerApplicationContext) applicationContext).getWebServer().getPort();
def webClient = WebClient.builder().baseUrl(sprintf("http://localhost:%d", port)).build();
webClient
.get().uri("/")
.exchange()
.flatMap({ r -> r.bodyToMono(String.class) })
.doAfterTerminate({ -> CompletableFuture.runAsync(applicationContext.&close) })
.subscribe(System.out.&println)
}
}
I connect everything in a top level class call DemoApplication
and wire up the listener:
static void main(String[] args) {
def builder = new SpringApplicationBuilder(DemoApplication.class)
builder
.listeners(new ApplicationReadyListener())
.run(args)
}
By using Groovys "Grapes" (@Grab('org.springframework.boot:spring-boot-starter-webflux:2.2.1.RELEASE')
), this gives a runnable Groovy script:
groovy -Dlogging.level.root=WARN -Dspring.main.banner-mode=OFF solution.groovy
I included some configuration variables to turn off logging and Springs Banner, so that the application outputs only the greeting.
Bear in mind that this solution fires up a complete web stack and therefore is slower than probably any other Hello World program out there.
This one is a solution just for fun. Why not solving the thing with SQL?
I did this with PostgreSQL, which supports recursive common table expressions.
First, change into the directory of this solution, bring up a Docker container running the latest PostgreSQL version, copy the input.txt
into it and get a bash shell:
docker run -d -p 5432:5432 --name postgres1 postgres
docker cp input.txt postgres1:/tmp/
docker exec -it postgres1 bash
From there, we create a database and login:
createdb -U postgres aoc
psql -U postgres aoc
First, create a table and load the data:
CREATE TABLE modules(mass INTEGER);
COPY modules(mass) FROM '/tmp/input.txt';
First star is trivial:
SELECT sum(mass/3-2) FROM modules;
Second star is fun:
WITH RECURSIVE fuel_for_fuel(fuel) AS ( (1)
SELECT mass/3-2 from modules (2)
UNION ALL
SELECT * FROM ( (3)
SELECT fuel/3-2 AS fuel FROM fuel_for_fuel
) hlp WHERE fuel > 0
)
SELECT sum(fuel) from fuel_for_fuel;
1 | Declare a recursive common table expression |
2 | Define the initial value |
3 | Recursively select from fuel_for_fuel . The nested from clause is only needed to avoid having the equation thrice. |
Because? It’s fun.
I have written the solution with my 9 year old kid in Java’s JShell. The JShell makes it easy to focus on the main part (equation of fuel, mapping inputs), without going through the hassles of building classes and whatnot.
To run the final program, you need JDK 11.
Thanks to JEP 330, launching is as easy as java Solution.java
.
Please bare in mind, that one would possible optimize the computation a bit more, but I wanted my kid to understand it.
The equation was straight forward, that’s basic math:
UnaryOperator<Integer> computeFuel = mass -> mass/3 - 2;
Sadly, I cannot use the var
keyword to define variables from Lambdas.
We stored the input masses as input.txt
and used java.nio.file.Files
to read them line by line.
This gives a stream of strings.
I explained that those needed to be turned into numbers and we also spoke about basis of numbers.
For each item of the stream, our equation can be applied.
var fuelForMass = Files.readAllLines(Path.of("input.txt")).stream() (1)
.map(Integer::parseInt) (2)
.map(computeFuel) (3)
.reduce(0, Integer::sum); (4)
System.out.println("Fuel for mass " + fuelForMass);
1 | Read the file line by line |
2 | Parse the lines into Integers |
3 | Apply the equation |
4 | Reduce the values to one single value |
I was positivly suprised that a reduction was explainable in minutes with a piece of paper.
Star two was a bit more evolved. I wanted to avoid recursive lambdas. Those are possible (and rather easy in JShell, as JShell allows forward references), but hard to understand.
We first settled with the same stream and "flat mapping" each computed fuel value into a new stream, much like in the puzzle itself.
"Flat mapping" is the process of taking one element of a stream and create a new stream from it, thus fanning out the elements of the original stream.
While porting our solution from JShell into an executable Java, I accidently used the wrong variable as initial value for our inner stream (builder.add(fuelForMass);
instead of builder.add(fuelForIndividualMass);
).
I noticed that when playing around ideas to make it more readable.
Let’s see if can make things a bit more readable:
UnaryOperator<Integer> computeFuel = mass -> mass/3 - 2;
UnaryOperator<Integer> computeFuelForFuel = fuelForIndividualMass -> (1)
Stream
.iterate(computeFuel.apply(fuelForIndividualMass), fuel -> fuel > 0, computeFuel) (2)
.reduce(fuelForIndividualMass, Integer::sum); (3)
var totalFuel = Files.readAllLines(Path.of("input.txt")).stream()
.map(Integer::parseInt)
.map(computeFuel.andThen(computeFuelForFuel)) (4)
.reduce(0, Integer::sum);
System.out.println("Total fuel needed " + totalFuel);
1 | We define a second operator, computeFuelForFuel |
2 | That generates a stream of integers in the form of a glorified for-loop. The first argument is the initial value (computing the fuel for the inital fuel), the second argument checks if we need to compute more fuel, the third is the operator for the next value, that receives the previous one |
3 | We reduce it again, this time not with 0 as the initial value for the fuel, but with the original value we need for the module |
4 | Here we concatenate two operators for the mapping. The rest is the same is in the solution for star one. |
I was so emberrased for having commited the wrong solution, that I added some simple assertions to the file with values from the puzzle itself:
assert computeFuel.andThen(computeFuelForFuel).apply(1969) == 966;
assert computeFuel.andThen(computeFuelForFuel).apply(100756) == 50346;
assert Stream.of(1969, 100756).map(computeFuel.andThen(computeFuelForFuel))
.reduce(0, Integer::sum) == 51312;
Be aware that those preconditions are only asserted when you run Java with the -ea
flag.
They are not a replacement for real tests, but for this solution, they are good enough.
I’m quite positve suprised that a modern Java solution is a readable 13 lines.
The main problem to tackle today is to work on state but keeping the original state around for later use.
For today, I decided to use Kotlin.
The solution can be run as a Kotlin script: kotlinc -script solution.kts
To solve the problem, one needs to access arbitrary elements by index. So for that either an array fits nicely or an index accessible collection.
Reading the input is pretty much the same with modern days Java 8 / Java 11:
val program = File("input.txt").readText().split(",").map { it.trim().toInt() }
The program is a Kotlin List
, which by default is immutable.
That is nice, so we don’t change the original program by accident when run.
I was more focussed on writing idiomatic Kotlin code than writing something more elaborate than my straight forward solution.
The solution is a function taking in the program as a list and two additional Int
-parameter representing the noun
and verb
to set.
The function returns the memory after the program run as an IntArray
.
val ops : Array<(Int, Int) -> Int> = arrayOf(Int::plus, Int::times) (1)
fun run(program: List<Int>, noun: Int, verb: Int): IntArray {
val mem = program.toIntArray() (2)
mem[1] = noun
mem[2] = verb
for (i in 0..mem.size step 4) { (3)
val inst = mem.sliceArray(i.until(min(i + 4, mem.size))) (4)
when (inst[0]) { (5)
in 1..2 -> mem[inst[3]] = ops[inst[0]-1].invoke(mem[inst[1]], mem[inst[2]])
99 -> return mem
}
}
return mem;
}
1 | Define the supported operations (plus and times) |
2 | Create a mutable IntArray from the List<Int> so that we can modify it |
3 | Iterate over all instructions, which have a maximum length of 4 |
4 | Slice out one instruction. Care must be taken not to read over the end of the array when the op code is 99 |
5 | Use when as a statement, reacting on the range of supported opcodes and the special opcode to terminate the program |
The solution for the second star is a brute force nested loop:
for(noun in 0..99) {
for(verb in 0..99) {
val mem = run(program, noun, verb);
if(mem[0] == 19690720) {
println(100 * noun + verb)
break
}
}
}
There are by a high chance nicer solutions to that, but there’s only so much time in a lunch break. Note that I don’t have to read the file multiple times, as the program is stored in an immutable collection.
Today’s problem can be solved mathematically or by brute force. Either you need to compute intersections of segments in 2d (which isn’t that hard) or just search for intersecting points.
Before I even knew the second star puzzle I settled for the brute force searching for common points. The problem space is constrained to two wires anyway, so I didn’t care about memory usage.
Solution is written in Java 11 and can be executed with java Solution.java
.
The main idea is a point that can be moved to another point and creating a segment along the way:
static class Point {
final int x;
final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
List<Point> createSegment(String s) {
Function<Integer, Point> newPoint;
switch (s.charAt(0)) {
case 'R':
newPoint = i -> new Point(x + i, y);
break;
case 'L':
newPoint = i -> new Point(x - i, y);
break;
case 'U':
newPoint = i -> new Point(x, y + i);
break;
case 'D':
newPoint = i -> new Point(x, y - i);
break;
default:
newPoint = i -> this;
}
var steps = Integer.parseInt(s.substring(1));
var segment = new ArrayList<Point>(steps);
for (int i = 1; i <= steps; ++i) {
segment.add(newPoint.apply(i));
}
return segment;
}
}
The input is a fixed set of two wires, each on a line.
Two wires are represented as maps of Point
to Integer
(Map<Point, Integer>
), mapping each point to the number of steps it took to reach it.
The map per wire will not contain duplicate points (self intersections).
So reading and "creating" the wires look like this:
var wires = Files.readAllLines(Path.of("input.txt"));
var pointsOfWires = List.of(new HashMap<Point, Integer>(), new HashMap<Point, Integer>());
for (int i = 0; i < 2; ++i) { (1)
var wirePoints = pointsOfWires.get(i);
var startingPoint = new Point(0, 0); (2)
var steps = 0;
for (String segments : wires.get(i).split(",")) { (3)
for (Point l : startingPoint.createSegment(segments)) {
wirePoints.put(l, ++steps);
startingPoint = l; (4)
}
}
}
1 | Fixed number of wires |
2 | Build wire from central point |
3 | Create segments with the instructions from the input |
4 | Move starting point |
To find the intersecting point I use Java’s Set
, which cannot contain duplicate values.
First, add all points from wire 1, than retain only those that are in wire 2 as well.
var intersectingPoints = new HashSet<>(pointsOfWires.get(0).keySet());
intersectingPoints.retainAll(pointsOfWires.get(1).keySet());
For the first start, we have to sort them by "Manhattan distance" from central and take the first one out:
intersectingPoints.stream()
.map(p -> Math.abs(p.x) + Math.abs(p.y))
.sorted()
.findFirst()
.ifPresent(System.out::println);
Before reading puzzle two, I had only sets and didn’t store the steps. I didn’t like the solution, because the memory usage is horrible. But than, for the second star, we need the steps each wire needs to reach the intersection. Yeah! So with them being stored by wire, we can just map each intersection to the sum of steps each wire takes to get there and done:
intersectingPoints.stream()
.map(p -> pointsOfWires.get(0).get(p) + pointsOfWires.get(1).get(p))
.sorted()
.findFirst()
.ifPresent(System.out::println);
Yeah, password hacking! Why not choosing Ruby for iterating a lot of stuff? :)
Run the solution via ./solution.rb
or ruby solution.rb
.
You have to love the language for it’s expressiveness.
Star one basically reads like the conditions for the possible passwords:
passwords_matching_first_rules = (134792..675810).select do |v| (1)
pairs = v.to_s.chars.each_cons(2) (2)
pairs.any? { |p| p[0] == p[1] } && pairs.all? { |p| p[0].to_i <= p[1].to_i } (3)
end
puts "Star one #{passwords_matching_first_rules.count}"
1 | Create range reassembling my input values and select all values for which the block returns true |
2 | Turn each value into a string, get the cars from it and create pairs of consecutive chars |
3 | Return whether any of those pairs satisfies being identical and all of the pairs being consecutive numbers |
I refrained from using regex here. Finding pairs of numbers is rather easy (/(\d)\1/
), but checking if there are lower or equal? Naaa…
However, I used regex for star two:
number_of_passwords_matching_third_rule = passwords_matching_first_rules.count do |p|
p.to_s.scan(/((\d)\2+)/).map{ |a| a[0]}.any? { |v| v.length == 2 } (1)
end
puts "Star two #{number_of_passwords_matching_third_rule}"
1 | Find all numbers repeated at last two times in the string and check if any of those matches is exactly 2 chars long. |
The solution uses the previous list of numbers and applies the count method with a block, counting only those elements that return true for the block.
The regex /((\d)\2+)/
needs some explanation: It consists of two capturing groups. The outer one being number one, enclosing everything matched, and the inner one being number two and matching a single digit \d
.
\2
is a back reference to this second group and needs to be at least once in the string.
scan
returns an array of arrays (all matches, presented as an item in an array for each group).
That was way easier for me than day 3.
Our teamlead teased and nerd sniped me a bit this week. Scala was the topic. I tend to make this joke now for some time: "Can you read my code? Shall I increase the font? Can you read it now?" - "No, it’s still Scala". I never wrote a Scala program until yesterday, actually, and I possible never tried hard enough to understand at least a bit about it.
I plan on getting Daniels book Scala From Scratch respectively The Neophyte’s Guide to Scala. Daniel is one of the friendliest developers I know and I guess his books will be a good, gentle introduction. |
Anyway, the solution to day 5 is my second try with Scala and I behaved like a kid in a sweet shop, trying a bit from everything. So please excuse me, when it’s not exactly the Scala way.
Day 5 builds on 1202 Program Alarm in which a programmable integer computer has to be build.
That computer has mutable memory, which exposes an interesting design decision upfront for a Scala program:
All the cool iterables, Seq
, List
and so on, are immutable.
I could copy them over and over again, but honestly: That just doesn’t feel right to me.
Therefore, I chose an Array
as memory.
I cannot add or remove elements on the same instance, but elements are mutable.
Good enough for the task at hand.
A bit of ceremony is needed to turn the Scala-file into an executable script. So be it.
object Solution extends App {
}
Reading stuff seems to be the everywhere. I misst try-with-resources
, though:
val input = io.Source.fromFile("input.txt")
val instructions = input.getLines().flatMap( _.split(",")).map(_.trim).map(_.toInt).toSeq
input.close
However, the underscore sticks out. What the? Turns out, a lot of ussages, see this SO answer. In the example above, it’s just used as Method values.
It became apparent, that todays puzzle would add a lot of operators, so this time I wanted them to objects and I wanted to use Case Classes for them and if possible, work with them in a pattern match.
I started with a basic operation:
abstract class Operation { (1)
def numargs(): Int (2)
def execute(pos: Int, args: Seq[(Int, Int)], reader: (((Int, Int)) => Int), writer: (Int, Int) => Unit): Int
}
1 | An abstract class |
2 | An abstract method, This method has 4 arguments: The current position, the arguments for the operation (a sequence of tuples of ints), a reader from the memory and a writer to the memory. Reader and writer are of type function: The reader from one tuple of ints to an int, the writer takes two ints. The tuples contain the position in memory in the first place, and in the second, the access mode. |
The execute
method is meant to execute the operation and returns the the next position.
Now, how does an operation look?
Star one requires all operations from day 2 (add
, mul
and exit
) as well as an input and an echo operation.
The exit operation is the easiest one. It does nothing:
case class Exit() extends Operation {
def numargs(): Int = 0
def execute(pos: Int, args: Seq[(Int, Int)], reader: (((Int, Int)) => Int), writer: (Int, Int) => Unit): Int = 0
}
I want some more traits for the numerics operation. Traits are used to share interfaces and fields between classes. They are similar to Java 8’s interfaces. Classes and objects can extend traits but traits cannot be instantiated and therefore have no parameters.
trait HasOpcode {
def opcode(): Int (1)
}
trait NumericOperation extends HasOpcode {
val ops = Seq[(Int, Int) => Int](_ + _, _ * _) (2)
def numargs(): Int = 3 (3)
def opcode(): Int
def execute(pos: Int, args: Seq[(Int, Int)], reader: (((Int, Int)) => Int), writer: (Int, Int) => Unit): Int = {
writer(args(2)._1, ops(opcode() - 1)(reader(args(0)), reader(args(1)))) (4)
pos + numargs() + 1
}
}
1 | Some more methods |
2 | A member variable representing the available ops, here + and * , the functions using the _ as placeholder for the actual arguments |
3 | A function with a default implementation |
4 | And the default execute method for a numeric operation.
This selects the the operation based on the opcode, applies it by reading stuff from the memory
and writing it back. |
The concrete ops look like this then:
case class Add() extends Operation with NumericOperation {
def opcode() = 1
}
Traits extends other traits, classes extends other classe and are with Traits. Much like extends and implements in Java.
Now, how to get such an operation?
I defined a Companion object for the class
and an apply
method.
The apply
method bridges between function and object oriented programming and creates instances of an object.
Here, it is a beautiful representation of a factory method, determining via a pattern match which concrete instance to create:
object Operation {
def apply(pos: Int, memory: Array[Int]) = memory(pos) % 100 match {
case 1 => Add()
case 2 => Mul()
case 3 => ReadInto()
case 4 => Echo()
case 99 => Exit()
case opcode@_ => throw new Exception(s"Invalid opcode $opcode")
}
}
This allows for writing val op = Operation(1, Array(3))
instead of throwing news`around.
The last `case
with the @
assigns the matched value to the variable before so that I can use it.
Before I show the actual program run, here are the functions I use to read and write from the memory, according the modes defined in the puzzle (position mode
(use the value at the given memory adress) or immediate
(use the value itself)).
def read(memory: Array[Int])(p: Int, mode: Int) =
if (mode == 0) memory(p) else p (1)
def write(memory: Array[Int])(p: Int, v: Int) =
memory(p) = v
val reader = (read(memory) _).tupled (2)
val writer = write(memory) _ (3)
1 | Definition of methods with multiple parameter lists |
2 | tupled is helpful: The partial function originally takes 2 parameters.
After that, it takes a Tuple2 , which we gonna use in a second. |
3 | Partial application of the write methode, with only the memory passed to it, effectivly turning it into an accessor or writer. |
They look funny… Both have two parameter lists, the first taking in the memory, the second one positional and mode respectively positional and value parameter. Those are methods that applicable to partial application (currying).
The last helper method I have extracts the parameter modes:
def extractModes(of: Int) = for (i <- 3 to 5) yield
of % Math.pow(10, i).toInt / Math.pow(10, i - 1).toInt
It uses a for-comprehension, something I heard at my work at Neo4j for the first time. We support this in Cypher to generate new patterns.
Adding up gives the following program, representing our computer:
def run(memory: Array[Int], pos: Int = 0): Unit = {
val operation = Operation(pos, memory)
val modes = extractModes(memory(pos))
val args = (1 to operation.numargs).map(i => (memory(pos + i), modes(i - 1))) (1)
val reader = (read(memory) _).tupled (2)
val writer = write(memory) _ (3)
operation match { (4)
case Exit() => Nil
case op@_ => {
val next = op.execute(pos, args, reader, writer)
run(memory, next)
}
}
}
1 | Create tuples of parameters and their mod |
2 | Create reader |
3 | and writer |
4 | match on the operation and either exit or execute and run again |
To run it, we create an array copy from the instructions read at the start and begin with:
println("For star one, enter 1, for start two, 5")
run(instructions.toArray)
Star two just added additional ops, you’ll find them in the source of course.
Other valid programs are (when loaded Solution.scala
into the Scala REPL)
:load Solution.scala
// Using position mode, consider whether the input is equal to 8; output 1 (if it is) or 0 (if it is not).
Solution.run(Array(3,9,8,9,10,9,4,9,99,-1,8))
// Using position mode, consider whether the input is less than 8; output 1 (if it is) or 0 (if it is not).
Solution.run(Array(3,9,7,9,10,9,4,9,99,-1,8))
// Using immediate mode, consider whether the input is equal to 8; output 1 (if it is) or 0 (if it is not).
Solution.run(Array(3,3,1108,-1,8,3,4,3,99))
// Using immediate mode, consider whether the input is less than 8; output 1 (if it is) or 0 (if it is not).
Solution.run(Array(3,3,1107,-1,8,3,4,3,99))
// And a bigger example, indidicating less than, equal or greater than 8
Solution.run(Array(3,21,1008,21,8,20,1005,20,22,107,8,21,20,1006,20,31,
1106,0,36,98,0,0,1002,21,125,20,4,20,1105,1,46,104,
999,1105,1,46,1101,1000,1,20,4,20,1105,1,46,98,99))
And that concludes my adventure in Scala world.
Run with scala Solution.scala .
Tested with Scala code runner version 2.13.1
Correct solutions are: 12896948 and 7704130
|
I was so hoping for something to be solved with the help of my favorite database and query language. Enter Neo4j and Cypher.
As with my SQL solution for day1, I’m gonna run all of this in a dockerized version of the database.
docker run -d -p 7474:7474 -p 7687:7687 -e 'NEO4J_AUTH=neo4j/secret' --name neo4j1 neo4j:3.5.13 (1) docker cp input.txt neo4j1:/var/lib/neo4j/import (2) docker exec -it neo4j1 cypher-shell -u neo4j -p secret (3)
1 | Startup a Neo4j container, publish the HTTP and Bolt ports and define a passwort (secret ) |
2 | Copy the input file into Neo4j’s import directory |
3 | Login into the container and open the cypher shell for executing commandos. |
I’m gonna use the shell, but you can of course also use the Neo4j browser. It has a friendly interface to get you started with your first graph and some nice visualisations. Here’s the one from the puzzle’s description:
I’m gonna use LOAD CSV
as described (among other things) in one of my Neo4j related talks.
LOAD CSV
processes CSV files line by line and takes in an optional field separator, which will be )
in our case:
LOAD csv FROM 'file:///input.txt' AS orbits FIELDTERMINATOR ')'
MERGE (o0:Object {name: orbits[0]}) (1)
MERGE (o1:Object {name: orbits[1]})
MERGE (o0) <- [:ORBITS] - (o1) (2)
RETURN COUNT(*);
1 | Create Object nodes. We use a merge statement, that checks whether a given pattern exists or not to avoid duplicates |
2 | Create the ORBITS relationship between the objects.
We could have done all of this in one step, but than nodes would have been duplicated, as MERGE always checks for
the existence of the whole pattern. |
Having the data, both star one and star two are rather short Cypher statements:
MATCH (c:Object {name: 'COM'}) WITH c
MATCH p=((:Object) -[:ORBITS*1..] -> (c))
RETURN sum(length(p));
This reads: "Match the object named 'COM' (Center of Mass), than match the path from all other objects to this object and return the sum of the length of those paths.". This works because the input is a tree respectivly a directed acyclic graph, to be precise.
Star two uses the built-in shortestPath
operation
MATCH (y:Object {name: 'YOU'}) -[:ORBITS] -> (o1:Object) (1)
MATCH (s:Object {name: 'SAN'}) -[:ORBITS] -> (o2:Object)
WITH o1, o2 (2)
MATCH p = shortestPath((o1)-[:ORBITS*1..]-(o2)) (3)
RETURN length(p);
1 | Find the starting objects (one step away from YOU and SAN ) |
2 | Pass them on to the next section of the query |
3 | Match the shortest path and return its length. |
And with the SQL example from day 1: Don’t just dump unstructured data into your databases. Use them the way they where meant to be used.
Tested with neo4j:3.5.13
Correct solutions are: 135690 and 298 .
|
Shit. Still in Scala land: Today’s task is continues at day 5. So, let’s see if I can manage.
We are speaking about a "real" compute more and more. To solve the task, we need persistent memory and address pointers, and also input.
First, my stack based IO, implememented with a list:
class IO(private var input: Seq[Int] = Seq()) {
def read: Option[Int] = input match {
case Nil => None
case l@_ => {
input = input.tail
Some(l.head)
}
}
def <<(v: Int) = { (1)
input = v +: input
this
}
}
1 | I like that: Creating own operators. Sadly, I cannot have infix operators with the same symbols, therefore the read |
The operations are the same as in day 5, but I added a whole computer class to stuff them together:
class Computer(private val memory: Array[Int]) { (1)
private def extractModes(of: Int) = for (i <- 3 to 5) yield
of % Math.pow(10, i).toInt / Math.pow(10, i - 1).toInt
val stdIo = new IO (2)
private var next: Option[Int] = Some(0)
private def load(p: Int, mode: Int) = if (mode == 0) memory(p) else p
private def store(p: Int, v: Int) = memory(p) = v
def run(): Option[Int] = {
val reader = (load _).tupled
val writer = store _
def executionInstruction: (Int => Option[Int]) = (ptr: Int) => { (3)
val operation = Operation(ptr, memory)
val modes = extractModes(memory(ptr))
val args = (1 to operation.numargs).map(i => (memory(ptr + i), modes(i - 1)))
operation match {
case Exit() => None
case op@Echo() => Some(op.execute(ptr, args, reader, writer, stdIo)) (4)
case op@_ => {
val next = op.execute(ptr, args, reader, writer, stdIo)
executionInstruction(next)
}
}
}
next = next.flatMap(executionInstruction)
stdIo.read (5)
}
def expectsInput() = next.isDefined
}
object Computer {
def loadProgram(instructions: Seq[Int]) = new Computer(instructions.toArray) (6)
}
1 | Computer has memory |
2 | It’s own IO |
3 | This executes the current instruction and returns the next pointer |
4 | This is actually already part of star two: The computer pauses, when it echoed some output instead of immediate jumping to the next instruction |
5 | Return the last output |
6 | Convinence method to load a program |
I didn’t find star one too hard:
def runProgram = (input: Int, computerAndPhase: (Computer, Option[Int])) => {
val c = computerAndPhase._1
c.stdIo << input
computerAndPhase._2 match {
case v@Some(_) => c.stdIo << v.get
case _ =>
}
c.run().get
} (1)
def runWithoutFeedback(instructions: Seq[Int]): Int = {
val executeSequence = (computers: Seq[(Computer, Option[Int])]) =>
computers.foldLeft(0)(runProgram) (2)
(0 to 4).map(Option(_))
.permutations (3)
.map((1 to 5).map(_ => Computer.loadProgram(instructions)).zip(_)) (4)
.map(executeSequence).max (5)
}
val starOne = runWithoutFeedback(instructions)
println(s"Star one ${starOne}")
1 | A run program function, takes an input (0 or the output from the computer machine) and the computer and an optional phase to init the computer
If there’s a phase, we use it.
The runProgram function returns the input for the next run. |
2 | Generator for 5 computers with the identical program each |
3 | A function that executes a sequence of phase, stored in a list of pairs (computer and phase). That list is folded from the left, with initial input of 0 |
4 | Scala has a build in method for permutating lists. That is nice and I use that |
5 | Init 5 new computers and zip them each with their phase |
6 | Execute each sequence of computers and phase and determine the maximum, reachable output |
The second star took me the whole afternoon… The idea is to initialize all computers.
Each get started with their assigned test phases.
When they are used up, only None
are generated, so that runProgram
doesn’t use it.
Than, loop as long as the last computer expects more input:
def runWithFeedback(instructions: Seq[Int]): Int = {
val executeSequence2 = (phases: Seq[Option[Int]]) => {
val computers = (1 to 5).map(_ => Computer.loadProgram(instructions))
val availablePhases = (phases ++: LazyList.continually(None)).iterator (1)
val nextRun = () => computers.last.expectsInput()
var lastOutput = 0
while (nextRun()) {
lastOutput = computers
.zip(availablePhases)
.foldLeft(lastOutput)(runProgram) (2)
}
lastOutput
}
(5 to 9).map(Option(_)).permutations.map(executeSequence2).max
}
val starTwo = runWithFeedback(instructions)
println(s"Star two ${starTwo}")
1 | A lazy list. This is quite cool. |
2 | Here the previous input gets feed back into the first computer (as the initial value for the fold) |
Other valid programs are (when loaded Solution.scala
into the Scala REPL)
:load Solution.scala
// 43210
Solution.runWithoutFeedback(Seq(3,15,3,16,1002,16,10,16,1,16,15,15,4,15,99,0,0))
// 54321
Solution.runWithoutFeedback(Seq(3,23,3,24,1002,24,10,24,1002,23,-1,23,
101,5,23,23,1,24,23,23,4,23,99,0,0))
139629729
Solution.runWithFeedback(Seq(3,26,1001,26,-4,26,3,27,1002,27,2,27,1,27,26,
27,4,27,1001,28,-1,28,1005,28,6,99,0,0,5))
// 18216
Solution.runWithFeedback(Seq(3,52,1001,52,-5,52,3,53,1,52,56,54,1007,54,5,55,1005,55,26,1001,54,
-5,54,1105,1,12,1,53,54,53,1008,54,0,55,1001,55,1,55,2,53,55,53,4,
53,1001,56,-1,56,1005,56,6,99,0,0,0,0,10))
Run with scala Solution.scala .
Tested with Scala code runner version 2.13.1
Correct solutions are: 92663 and 14365052
|
Run with java --source 13 --enable-preview Solution.java .
Tested with java version "13" 2019-09-17
Correct solutions are: 92663 and 14365052
|
I think that’s maybe the second or third time I ever actively used Python. Significant spaces: Is this YAML in disguise ;)?
The idea of the solution: Split the input into chunks the size of the images dimensions, sort by number of zeros and take the first. Second star uses an image mask.
#!/usr/bin/env python3
w = 25
h = 6
d = w * h
def chunks(lst, n):
"""Yield successive n-sized chunks from lst.
This solution is from Andreas Zwinkau (https://github.com/qznc)
I used `wrap` from textwrap before, but that doesn't play nice with whitespaces
(Well, it does, but more for like text processing).
Many other inputs have been very messed up and hardly readable.
https://aoc-2019.netlify.com/generated/coder/qznc/generateddays#_day_08_python
"""
for i in range(0, len(lst), n):
yield lst[i:i + n]
with open('input.txt', 'r') as file:
input = file.read().rstrip()
layers = list(chunks(input, d)) (1)
mostTransparentLayer = sorted(layers, key=lambda s: s.count("0"))[0] (2)
starOne = mostTransparentLayer.count("1") * mostTransparentLayer.count("2")
print(f"Star one {starOne}")
translation = {"0": " ", "1" : "*", "2": "2"} (3)
image = ["2" for _ in range(d)] (4)
px = 0
for layer in layers:
for px in range(0, d):
image[px] = translation[layer[px]] if image[px] == "2" else image[px]
starTwo = "\n".join(chunks("".join(image), w)) (5)
print("Star two")
print(starTwo)
1 | Create the layers by chunking the input at the given dimension (Please note the comment at the chunk method). chunk returns a generator. This can be iterated, but cannot be reseted. I need the list of layers twice, so I copy them into a list |
2 | Create a sorted copy of that list (by the count of 0 ) |
3 | I use a dictionary for translating the image value |
4 | Start with a transparent image as mask. Again. we find a list comprehension during AoC |
5 | Here, we wrap the final image, this time at the width and than join those lines together by \n |
The image mask array could have been generated by unpacking the list like this [*map(lambda item: "2", range(d))]
, but I like the list comprehension more.
Run with python3 Solution.py .
Tested with Python 3.7.0
|
Say Intcode computer one more time…
Anyway.
Today, we got relative parameter modes and a bigger address space. I build this on my Java solution for day 7.
Bigger address space has been solved with a view on the runnning program:
private final Map<Long, Long> memory;
private long resolveAddress(long address, int mode) {
return mode == 2 ? base + address : address; (1)
}
private Long load(long p, int mode) {
if (mode == 1) {
return p;
}
var address = resolveAddress(p, mode);
return this.memory.computeIfAbsent(address, i -> i > program.length ? 0L : program[Math.toIntExact(i)]);
}
private void store(long p, int mode, Long v) {
var address = resolveAddress(p, mode);
this.memory.put(address, v);
}
1 | Implementing relative memory in one go |
The listing also shows the relative memory. One more new instruction to modify it:
case 9 -> {
var l = load(load(ptr++, 0), modes[0]);
base += l;
yield Optional.of(ptr);
}
I also split my io
stack into both stdIn
and stdOut
and throw an exception
when the computer requries input:
case 3 -> {
var v = readStdIn().orElseThrow(InputRequiredException::new);
store(load(ptr++, 0), modes[0], v);
yield Optional.of(ptr);
}
Reading and writing to standard io is now internal to the computer.
Input can be piped into it. The computer doesn’t return anything now on run.
Output can be accessed via head()
or drain()
:
private Optional<Long> readStdIn() {
return Optional.ofNullable(stdIn.poll());
}
private void writeStdOut(Long in) {
stdOut.push(in);
}
public Computer pipe(Long in) {
stdIn.push(in);
return this;
}
public List<Long> drain() {
var output = new ArrayList<Long>(this.stdOut.size());
this.stdOut.descendingIterator().forEachRemaining(output::add);
this.stdOut.clear();
return output;
}
public Optional<Long> head() {
return Optional.of(this.stdOut.poll());
}
I changed this from day 7, so that I can use the amplication circuit with todays computer. Todays computer should not halt after it produced output, in my day 7 solution, it needed to halt.
Biggest challenge today? Can you spot the bug in my day 7 solution? Well, I couldn’t for some time. There’s one tiny, little instruction that just didn’t get hit on day 7. Well, now it does. |
For the second star, I needed to drop the recursion, as the stack size went too big.
Run with java --source 13 --enable-preview Solution.java .
Tested with java version "13" 2019-09-17 , correct solutions are: 3601950151 and 64236 .
|
I like python.
Here’s the idea to solve this puzzle. First have some basic geometry:
def direction(v1, v2):
return (v2[0]-v1[0], v2[1]-v1[1])
def length(v):
return math.sqrt(v[0]**2 + v[1]**2)
def norm(t):
l = length(t)
return (round(t[0]/l,4), round(t[1]/l,4))
Than
all = {}
for candidate in asteroids: (1)
targets = {}
for other in asteroids:
if(candidate == other):
continue
d = direction(candidate, other) (2)
targets.setdefault(norm(d), []).append((other, length(d))) (3)
for val in targets.values(): val.sort(key=lambda i:i[1]) (4)
all[candidate] = targets
base = max(all.items(), key=lambda i:len(i[1])) (5)
print(f"Star one {len(base[1])}")
1 | Create the cross product of each asteroid with all others. |
2 | Compute the direction and the norm of the directional vector for each pair. |
3 | Store all targets per direction into a list |
4 | Sort that list by direction |
5 | Select the one that has most outgoing directions |
That’s the base. From there on:
hit = []
while(len(hit) < 200 and len(base[1]) > 0):
for val in sorted(base[1].keys(), key=lambda i:(math.atan2(i[0], i[1])), reverse=True): (1)
targeted = base[1].get(val)
hit.append(targeted.pop()[0]) (2)
if(not targeted):
del base[1][val]
if(len(hit) == 200):
break
print(f"Star two {hit[-1][0]*100+hit[-1][1]}")
1 | Go through all targeteting directions, counterclockwise |
2 | Hit the first |
3 | And move on. |
Run with python3 Solution.py . Tested with Python 3.7.0 .
Correct solutions are 263 and 1110 ,
expected solutions for test-input2.txt are 210 and 802
and 30 and 1203 for test-input3.txt
|
I'm getting slightly nervous ticks… #IntComputer #AdventOfCode
— Michael Simons (@rotnroll666) December 11, 2019
I could reuse my day 9 solution completely, which is kinda good, I guess.
I added a reset
method to the computer, so that I don’t need to reload the program each time.
Nothing special to the solution of the puzzle. Basic walking around stuff. As I will stick in Javaland with IntComputer tasks, I might as well do it nicely object oriented with a bunch of classes:
enum Direction {
LEFT, RIGHT;
static Direction fromLong(long instruction) {
return instruction == 0L ? LEFT : RIGHT;
}
}
enum View {
NORTH("^"), SOUTH("v"), WEST("<"), EAST(">");
private final String rep;
View(String rep) {
this.rep = rep;
}
View turn(Direction direction) {
return switch (this) {
case NORTH -> direction == Direction.LEFT ? WEST : EAST;
case WEST -> direction == Direction.LEFT ? SOUTH : NORTH;
case SOUTH -> direction == Direction.LEFT ? EAST : WEST;
case EAST -> direction == Direction.LEFT ? NORTH : SOUTH;
};
}
@Override
public String toString() {
return this.rep;
}
}
static class Panel {
private final int x;
private final int y;
Panel(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
Panel next(View view) {
int dx = 0;
int dy = 0;
switch (view) {
case NORTH -> dy = 1;
case WEST -> dx = -1;
case SOUTH -> dy = -1;
case EAST -> dx = 1;
}
return new Panel(this.x + dx, this.y + dy);
}
}
You get the idea:
A Direction
is derived from an instruction,
a View
uses that direction to compute the next view
and a Panel
uses that to compute the next one.
Given an enum Color
with appropriate method to load and represent it self, the solution basically is the prose above:
enum Color {
BLACK, WHITE;
}
static Map<Panel, Color> walkHull(Computer computer, Map.Entry<Panel, Color> startPanel, Consumer<Event> callback) {
var panelComparator = Comparator
.comparing(Panel::getY).reversed().thenComparing(Panel::getX); (1)
var panels = new TreeMap<Panel, Color>(panelComparator);
Consumer<Event> handler = event -> {
if (event instanceof PanelPaintedEvent) {
PanelPaintedEvent panelPaintedEvent = (PanelPaintedEvent) event;
panels.put(panelPaintedEvent.getPanel(), panelPaintedEvent.getPayload());
}
};
handler = handler.andThen(callback);
var currentView = View.NORTH;
var currentPanel = startPanel.getKey();
var currentColor = startPanel.getValue();
// Need to initialize the start color
handler.accept(new PanelPaintedEvent(currentPanel, currentColor));
do {
// Compute
computer
.pipe(panels.getOrDefault(currentPanel, Color.BLACK).toLong()) (2)
.run(false);
var output = computer.drain();
// Paint
currentColor = Color.fromLong(output.get(0));
handler.accept(new PanelPaintedEvent(currentPanel, currentColor));
// Move
currentView = currentView.turn(Direction.fromLong(output.get(1)));
currentPanel = currentPanel.next(currentView);
handler.accept(new MovedToPanelEvent(currentPanel, currentView));
} while (computer.expectsInput());
handler.accept(new StoppedEvent(currentPanel));
computer.reset();
return panels;
}
1 | Probably the most intesting thing here: Creating a comparator for the panels.
This saves me from adding equals and hashCode to panel, as I use a TreeMap for storing them.
The TreeMap identifies two objects as equal when they are comparing to 0 .
I could Panel let implement Comparable , but than the comparision would always be the same.
For my usecase (painting / printing) them, I want to to be ordered by their y -axis in reversed order first, than by x . |
2 | I don’t use computeIfAbsent here as I’m gonna put the panel into the map anyway, thus avoiding one operation. |
I think that the above solution is a nice way of speaking the domains language without creating ivory towers.
Bonus: I added some handler code that gets each state event and can create this:
Run with java --source 13 --enable-preview Solution.java .
Tested with java version "13" 2019-09-17 , correct solutions are: 1732 and ABCLFUHJ .
|
I had help today on a business trip to Malmö and I liked it very much:
On our way to Malmö for the Christmas party with my wonderful #Neo4j colleagues. Solving #AdventOfCode on the way with @tinasimons 😂 pic.twitter.com/nf8esydqLB
— Michael Simons (@rotnroll666) December 12, 2019
Honestly, I would have come up with a solution for part 2 today without staring at the slowly increments of by brute force loop with my wife seeing that the axis and their velocities are independent.
And yes, Eric Wastl was very right with:
This set of initial positions takes 4686774924 steps before it repeats a previous state! Clearly, you might need to find a more efficient way to simulate the universe.
Today we used Ruby again. Some highlights:
class Array
def add(other)
[self,other].transpose.map { |x| x.reduce(:+) }
end
end
Open classes, yeah! Here adding a pairwise add
to Rubys array class
We added some classes that allow adding and applying gravity and velocity like this:
# Other methods obmitteted for brevity
class Moon
attr_reader :position
attr_reader :vel
attr_reader :gravity
def add_gravity_of(other_moon)
@gravity = @gravity.add([
compare_value_of_axis(@position[0], other_moon.position[0]),
compare_value_of_axis(@position[1], other_moon.position[1]),
compare_value_of_axis(@position[2], other_moon.position[2]),
])
self
end
Than first part is fairly trivial and again, readable as in the puzzle:
moons = input.map(&:clone) (1)
(1..1000).each do
moons
.product(moons).select { |p| p[0] != p[1] } (2)
.each{ |m1,m2| m1.add_gravity_of(m2) }
moons.each{ |m| m.apply_gravity.apply_velocity }
end
total_energy = moons.map(&:energy).reduce(:+) (3)
puts "Star one #{total_energy}"
1 | Cloning the input because we need it unmodified later |
2 | Create pairs of moons and apply their gravity onto each other |
3 | This reduction is nice: Map take’s a block taking the mapped value,
but we can use & to turn the element of the block into a proc and calling
the symbolic name energy (via the : ) on it, that happens to be our computation for the enegery. |
Second star was hard for me to spot… Implementation than was easy:
moons = input.map(&:clone)
cnt = 0
cycles = [-1, -1, -1]
c = [nil, nil, nil]
axes = Array.new(3) { Set.new }
loop do
moons
.product(moons).select { |p| p[0] != p[1] }
.each{ |m1,m2| m1.add_gravity_of(m2) }
moons.each{ |m| m.apply_gravity.apply_velocity }
(0..2).each.select { |i| cycles[i] < 0}.each do |i| (1)
axis = moons.map { |m| [m.position[i], m.vel[i]].flatten }
cycles[i] = cnt if axes[i].include? axis
axes[i].add(axis)
end
cnt += 1
break if moons == input or cycles.all? {|v| v != -1} (2)
end
cycles_until_start = cycles.reduce(&:lcm) (3)
puts "Star two #{cycles_until_start}"
1 | On three axis independently, check whether it has seen before in this combination (combination of all moon’s axis in question) |
2 | Break if the moons happened to be back or when all axis have completed a cycle |
3 | Reduce the number of cycles to their least common multple |
Fun stuff to do and I’m really happy about it.
Tested with ruby 2.6.3p62
Correct solutions are: 7013 and 324618307124784
|
God damn it. I had to fix the comparision method YET ANOTHER TIME.
Why? Because I compared to Long
with ==
and not equals
(from before I moved to virtual memory I used long
.
So why does this work at times and sometimes don’t?
Java keeps a cache for Integer
and Long
instances. The caches reaches from -128 upto 127 (the high mark is configurable for integers).
So, two different instances of Integer
or Long
with the same value compare to true if they are in that range, otherwise not (because ==
compares addresses).
The numerical operators (<
, >
, ⇐
, >=
) are not affected, because auto-unboxing kicks in for them.
Yay. I was so damn annoyed because I actually know that stuff.
Anyway, after that, the Int-Computer didn’t require any changes, the solution to part 1 is trival. Just count every third output if it is a block:
var computer = Computer.loadProgram(instructions);
var output = computer.run().drain();
int cnt = 0;
for (int i = 0; i < output.size(); ++i) {
if (i % 3 == 2 && TileType.fromLong(output.get(i)) == TileType.BLOCK) {
++cnt;
}
}
System.out.println(String.format("Star one %d", cnt));
The second part? Really? Playing brick out on that thing? Naaah.
instructions.set(0, 2L); (1)
var paddlePattern = List.of(TileType.EMPTY.toLong(), TileType.PADDLE.toLong(), TileType.EMPTY.toLong()); (2)
for (int i = 0; i < instructions.size(); i += 2) {
if (instructions.subList(i, i + 3).equals(paddlePattern)) { (3)
var paddleAt = i + 1;
var j = paddleAt;
while (instructions.get(--j) != TileType.WALL.toInt()) {
instructions.set(j, TileType.PADDLE.toLong());
}
j = paddleAt;
while (instructions.get(++j) != TileType.WALL.toInt()) {
instructions.set(j, TileType.PADDLE.toLong());
}
break;
}
}
var computer = Computer.loadProgram(instructions); (4)
while (computer.run(false).expectsInput()) {
computer.pipe(0L); (5)
}
computer.head()
.ifPresent(d -> System.out.println(String.format("Star two %d", d)));
1 | The puzzle say you have to change the instructions 🤔 Why not hack all of the instructions? |
2 | See if we find the game data: An empty thing, followed by the paddle and another empty thing. |
3 | Make the paddle a bit bigger… until it reaches walls on both sides |
4 | Load the new instructions |
5 | Run and never move |
6 | Last output is the high score after destroying all blocks. |
Run with java --source 13 --enable-preview Solution.java .
Tested with java version "13" 2019-09-17 , correct solutions are: 326 and 15988 .
java --source 13 --enable-preview Solution.java --animate gives you an animated version.
|