Day 7 Off-heap
This commit is contained in:
234
src/main/java/de/advent_of_code_2025/day7/Main3.java
Normal file
234
src/main/java/de/advent_of_code_2025/day7/Main3.java
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user