1
0

Day 7 Off-heap

This commit is contained in:
2025-12-12 08:28:50 +01:00
parent ebddbc1de5
commit e4de2b844b

View File

@@ -0,0 +1,234 @@
package de.advent_of_code_2025.day7;
import de.advent_of_code_2025.util.InputReader;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.atomic.AtomicLong;
public class Main3 {
private static final int SKIP_ROWS = 2;
private static final long ALIGNMENT = 16;
private static boolean[][] SPLITTER_POSITIONS;
private static byte WIDTH; // Number of bytes in a column
private static int RELEVANT_ROWS;
static void main(String[] args) throws Throwable {
final List<String> lines = InputReader.read(args);
final int rows = lines.size();
final int columns = lines.getFirst().length();
WIDTH = (byte) Math.round(((float) columns) / 8); // Round up, since there are no byte fractions
SPLITTER_POSITIONS = new boolean[rows - SKIP_ROWS][columns];
RELEVANT_ROWS = rows - SKIP_ROWS;
for(int i = SKIP_ROWS; i < rows; i++) {
for(int j = 0; j < columns - 1; j++) {
SPLITTER_POSITIONS[i - SKIP_ROWS][j] = lines.get(i).charAt(j) == '^';
}
}
final AtomicLong counter = new AtomicLong(0);
try(Arena arena = Arena.ofShared()) {
final MemorySegment initialSegment = allocateInitial(lines, arena, rows, columns);
for(int i = 0; i < SPLITTER_POSITIONS[0].length; i++) {
if(SPLITTER_POSITIONS[0][i]) {
try(var scope = StructuredTaskScope.open()) {
int finalI = i;
// Left
final MemorySegment leftSegment = copyAndSet(initialSegment, arena, 0, i - 1);
scope.fork(() -> travel(counter, leftSegment, arena, 2, finalI - 1));
// Right
final MemorySegment rightSegment = copyAndSet(initialSegment, arena, 0, i + 1);
scope.fork(() -> travel(counter, rightSegment, arena, 2, finalI + 1));
scope.join();
}
break;
}
}
}
System.out.println(counter.get());
}
private static final void travel(AtomicLong counter, MemorySegment current, Arena arena, int row, int column) {
if(row >= RELEVANT_ROWS - 1) {
counter.incrementAndGet();
printAsPuzzle(current);
return;
}
for(int i = column; i < SPLITTER_POSITIONS[row].length; i++) {
int finalI = i;
try(var scope = StructuredTaskScope.open()) {
if(SPLITTER_POSITIONS[row][i]) {
// Left
final MemorySegment leftSegment = copyAndSet(current, arena, row, i - 1);
scope.fork(() -> travel(counter, leftSegment, arena, row + 2, finalI - 1));
// Right
final MemorySegment rightSegment = copyAndSet(current, arena, row, i + 1);
scope.fork(() -> travel(counter, rightSegment, arena, row + 2, finalI + 1));
}
else {
// The cell under the beam is free
final MemorySegment leftSegment = copyAndSetOnce(current, arena, row + 1, i);
scope.fork(() -> travel(counter, leftSegment, arena, row + 1, finalI));
}
scope.join();
return;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private static final MemorySegment copyAndSetOnce(MemorySegment src, Arena arena, int row, int column) {
final MemorySegment copy = arena.allocate(src.byteSize(), ALIGNMENT).copyFrom(src);
set(copy, row, column);
return copy;
}
private static final MemorySegment copyAndSet(MemorySegment src, Arena arena, int row, int column) {
final MemorySegment copy = arena.allocate(src.byteSize(), ALIGNMENT).copyFrom(src);
set(copy, row, column);
set(copy, row + 1, column);
return copy;
}
private static final void set(MemorySegment segment, int row, int column) {
final long offset = calculateOffset(row, column);
final byte current = segment.get(ValueLayout.OfByte.JAVA_BYTE, offset);
//final byte bit = (byte) ((byte) 7 - (column - (column / 8)));
final byte bit = (byte) (column < 8 ? 7 - column : 7 - (column - ((column / 8) * 8)));
segment.set(ValueLayout.OfByte.JAVA_BYTE, offset, (byte) (current | (1 << bit)));
}
private static final long calculateOffset(int row, int column) {
return ((long) row * WIDTH) + (column / 8);
}
private static final MemorySegment allocateInitial(List<String> lines, Arena arena, int rows, int columns) {
// First row contains only the Starter
// Second row contains only the first single beam
// Third row contains the first splitter
// Every char in the puzzle is just a bit - that safes us a lot of memory
final MemorySegment initialSegment = arena.allocate((long) (rows - SKIP_ROWS) * WIDTH, ALIGNMENT);
// Initially everything is 0
// We do not mark splitters in the segment, because a bit can only be 0 or 1
// and 0 means "empty" while 1 means "beam"
// Splitters need to be taken from the SPLITTER_POSITIONS array
initialSegment.fill((byte) 0x00);
return initialSegment;
}
private static final String toBinaryString(MemorySegment segment) {
final long size = segment.byteSize();
final StringBuilder sb = new StringBuilder();
for(int i = 0; i < size; i++) {
// We use String.format because leading 0 are omitted
// We bitwise and with 0xFF to suppress sign extension
sb.append(String.format("%8s", Integer.toBinaryString(segment.get(ValueLayout.OfByte.JAVA_BYTE, i) & 0xFF)).replace(' ', '0'));
sb.append(" ");
}
return sb.toString();
}
private static final void printAsPuzzle(MemorySegment segment) {
final long size = segment.byteSize();
final StringBuilder sb = new StringBuilder();
final byte[] bytes = new byte[(int) size];
final Map<Integer, List<Byte>> rowMap = new HashMap<>();
for(int i = 0; i < size; i++) {
bytes[i] = (byte) (segment.get(ValueLayout.OfByte.JAVA_BYTE, i) & 0xFF);
}
int row = 0;
int previousRow = 0;
for(int i = 0; i < bytes.length; i++) {
row = i / WIDTH;
final Byte b = bytes[i];
rowMap.compute(row, (k, v) -> {
if(v == null) {
List<Byte> value = new ArrayList<>();
value.add(b);
return value;
}
v.add(b);
return v;
});
if(row != previousRow) {
previousRow = row;
}
}
for(Map.Entry<Integer, List<Byte>> e : rowMap.entrySet()) {
StringBuilder s = new StringBuilder();
for(Byte b : e.getValue()) {
s.append(String.format("%8s", Integer.toBinaryString(b.intValue() & 0xFF)).replace(' ', '0'));
}
String str = s.toString();
for(int i = 0; i < str.length(); i++) {
boolean isSplitter = false;
try {
isSplitter = SPLITTER_POSITIONS[e.getKey()][i];
}
catch(ArrayIndexOutOfBoundsException err) {
}
if(str.charAt(i) == '0' && isSplitter) {
sb.append("^");
}
else if(str.charAt(i) == '0') {
sb.append(".");
}
else {
sb.append("|");
}
}
sb.append("\r\n");
}
System.out.println(sb);
}
}