Skip to content

java: add solution for year 2016, day 25 #118

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
java_library(
name = "benchmark_library",
srcs = glob(["*.java"]),
deps = [
"@maven//:org_openjdk_jmh_jmh_core",
"//java/src/main/java/com/github/saser/adventofcode/year2016/day25:day25",
],
plugins = ["//java/src/benchmark:annotation_processor"],
data = ["//inputs:2016/25"],
)

java_binary(
name = "benchmark",
main_class = "org.openjdk.jmh.Main",
runtime_deps = [
"@maven//:org_openjdk_jmh_jmh_core",
":benchmark_library",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.github.saser.adventofcode.year2016.day25;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.FileSystems;
import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Warmup;

@BenchmarkMode(Mode.AverageTime)
@Fork(1)
@Measurement(iterations = 1, time = 1)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@Warmup(iterations = 5, time = 1)
public class Day25Benchmark {
private Reader input;

@Setup
public void setup() throws IOException {
var path = FileSystems.getDefault().getPath("inputs", "2016", "25");
var contents = Files.readString(path);
this.input = new StringReader(contents);
}

@Benchmark
public void part1() throws IOException {
Day25.part1(this.input);
this.input.reset();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,25 @@
import java.io.BufferedReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

public class VM {
public final String[][] program;
public int pc;
public int[] registers;

public VM(String[] program, int pc, int a, int b, int c, int d) {
this.program = splitProgram(program);
public VM(String[][] program, int pc, int a, int b, int c, int d) {
this.program = program;
this.pc = pc;
this.registers = new int[] {a, b, c, d};
}

public VM(String[] program) {
this(program, 0, 0, 0, 0, 0);
}

public VM() {
this(new String[0]);
this(splitProgram(program), 0, 0, 0, 0, 0);
}

public static VM from(Reader r) {
Expand All @@ -39,6 +38,33 @@ private static String[][] splitProgram(String[] program) {
.toArray(String[][]::new);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VM vm = (VM) o;
return pc == vm.pc &&
Arrays.equals(program, vm.program) &&
Arrays.equals(registers, vm.registers);
}

@Override
public int hashCode() {
int result = Objects.hash(pc);
result = 31 * result + Arrays.hashCode(program);
result = 31 * result + Arrays.hashCode(registers);
return result;
}

@Override
public VM clone() {
var program = new String[this.program.length][];
for (var i = 0; i < program.length; i++) {
program[i] = this.program[i].clone();
}
return new VM(program, this.pc, this.a(), this.b(), this.c(), this.d());
}

public int a() {
return this.register("a");
}
Expand Down Expand Up @@ -71,13 +97,23 @@ public void d(int i) {
this.register("d", i);
}

public void runAll() {
public void reset() {
this.pc = 0;
this.a(0);
this.b(0);
this.c(0);
this.d(0);
}

public List<Integer> runAll() {
var output = new ArrayList<Integer>();
while (this.pc < this.program.length) {
this.run();
this.run().ifPresent(output::add);
}
return output;
}

public void run() {
public Optional<Integer> run() {
var instruction = this.program[this.pc];
var op = instruction[0];
var param1 = instruction[1];
Expand All @@ -89,6 +125,7 @@ public void run() {
value2 = Optional.of(this.valueOf(param2.get()));
}
var delta = 1;
var out = Optional.<Integer>empty();
switch (op) {
case "cpy":
try {
Expand Down Expand Up @@ -128,10 +165,14 @@ public void run() {
}
break;
}
case "out":
out = Optional.of(value1);
break;
default:
throw new IllegalArgumentException(String.format("invalid op: %s", op));
}
this.pc += delta;
return out;
}

private int registerOffset(String x) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package(default_visibility = ["//visibility:public"])

java_library(
name = "day25",
srcs = glob(["*.java"]),
deps = [
"//java/src/main/java/com/github/saser/adventofcode/year2016/assembunny:assembunny",
"//java/src/main/java/com/github/saser/adventofcode:adventofcode",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.github.saser.adventofcode.year2016.day25;

import java.io.Reader;

import com.github.saser.adventofcode.Result;
import com.github.saser.adventofcode.year2016.assembunny.VM;

public final class Day25 {
// My initial attempt consisted of trying to find a loop in the states of
// the VM, based on the pattern `{0, 1}`. However, I expected the loop to be
// exactly as long as the pattern, i.e., 2 states.
//
// After some initial failures, I just ran the program for a couple of
// inputs and noted the values of the registers at different times, as well
// as what was output. Turns out that the output is the binary
// representation of a number, in reverse order, not limited to a certain
// number of bits. In other words, we do not care about leading zeroes.
//
// The number, which I decided to call `bound`, is equal to what we choose
// as `a`, plus a product of the values of `b` and `c` after an initial
// setup. This setup consists of 3 instructions, which is why the first
// three instructions are executed below.
//
// After we have found the bound, then we simply have to find the lowest
// integer that is at least as large as bound, and whose binary
// representation (without leading zeroes) is `10` repeating. To do that, we
// simply start by setting `a` to zero, and as long as a is less than
// `bound`, we shift `a` left two times, and add `10` as the new least
// significant bits. When we have found `a`, the answer is `a - bound`.
public static Result part1(Reader r) {
var vm = VM.from(r);
for (var i = 0; i < 3; i++) {
vm.run();
}
var bound = vm.b() * vm.c();
var a = 0;
while (a < bound) {
a <<= 2;
a |= 0b10;
}
return Result.ok(Integer.toString(a - bound));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.saser.adventofcode.year2016.assembunny;

import java.io.InputStreamReader;
import java.util.List;

import org.junit.Assert;
import org.junit.Test;
Expand Down Expand Up @@ -91,9 +92,98 @@ public void testJnzNotZero() {
Assert.assertEquals(2, vm.a());
}

@Test
public void testTglOneArgument() {
var program = new String[] {
"tgl 1",
"inc a", // will become `dec a`, producing a = -1
"tgl 1",
"dec b", // will become `inc b`, producing b = 1
"tgl 1",
"tgl c", // will become `inc c`, producing c = 1
"tgl 1",
"out d", // will become `inc d`, producing d = 1
};
var vm = new VM(program);
vm.runAll();
Assert.assertEquals(-1, vm.a());
Assert.assertEquals(1, vm.b());
Assert.assertEquals(1, vm.c());
Assert.assertEquals(1, vm.d());
}

@Test
public void testTglTwoArguments() {
var program = new String[] {
"tgl 1",
"jnz 1 a", // will become `cpy 1 a`, producing a = 1
"tgl 1",
"cpy 1 2", // will become `jnz 1 2`...
"cpy 1 b", // which will skip this instruction...
"cpy 2 b", // producing b = 2
};
var vm = new VM(program);
vm.runAll();
Assert.assertEquals(1, vm.a());
Assert.assertEquals(2, vm.b());
}

@Test
public void testTglSelf() {
var program = new String[] {
"tgl a", // a == 0, so this will become `inc a`
"dec a", // the previous instruction should be skipped, producing a = -1
};
var vm = new VM(program);
vm.runAll();
Assert.assertEquals(-1, vm.a());
}

@Test
public void testTglIllegal() {
var program = new String[] {
"tgl 1",
"jnz 1 2", // will become `cpy 1 2`, which is illegal and should be skipped...
"cpy 1 a", // producing a = 1
};
var vm = new VM(program);
vm.runAll();
Assert.assertEquals(1, vm.a());
}

@Test
public void testOutImmediate() {
var vm = VM.from("out 1");
var outputs = vm.runAll();
Assert.assertEquals(List.of(1), outputs);
}

@Test
public void testOutRegister() {
var program = new String[] {
"cpy 1 a",
"out a",
};
var vm = new VM(program);
var outputs = vm.runAll();
Assert.assertEquals(List.of(1), outputs);
}

@Test
public void testOutMultiple() {
var program = new String[] {
"out 1",
"out 2",
"out 3",
};
var vm = new VM(program);
var outputs = vm.runAll();
Assert.assertEquals(List.of(1, 2, 3), outputs);
}

@Test
public void testSetRegisters() {
var vm = new VM(new String[0], 0, 0, 0, 0, 0);
var vm = new VM(new String[0][0], 0, 0, 0, 0, 0);
vm.a(1);
Assert.assertEquals(1, vm.a());
vm.b(2);
Expand All @@ -104,6 +194,29 @@ public void testSetRegisters() {
Assert.assertEquals(4, vm.d());
}

@Test
public void testReset() {
var program = new String[] {
"cpy 1 a",
"cpy 2 b",
"cpy 3 c",
"cpy 4 d",
};
var vm = new VM(program);
vm.runAll();
Assert.assertEquals(1, vm.a());
Assert.assertEquals(2, vm.b());
Assert.assertEquals(3, vm.c());
Assert.assertEquals(4, vm.d());
Assert.assertEquals(4, vm.pc);
vm.reset();
Assert.assertEquals(0, vm.a());
Assert.assertEquals(0, vm.b());
Assert.assertEquals(0, vm.c());
Assert.assertEquals(0, vm.d());
Assert.assertEquals(0, vm.pc);
}

@Test
public void testDay12Example() {
var r = new InputStreamReader(this.getClass().getResourceAsStream("day12example"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
java_test(
name = "test",
srcs = glob(["*.java"]),
test_class = "com.github.saser.adventofcode.year2016.day25.Day25Test",
deps = [
"@maven//:junit_junit",
"//java/src/main/java/com/github/saser/adventofcode:adventofcode",
"//java/src/main/java/com/github/saser/adventofcode/year2016/day25:day25",
],
data = ["//inputs:2016/25"],
)
Loading