diff --git a/src/main/java/de/advent_of_code_2025/day7/Main3.java b/src/main/java/de/advent_of_code_2025/day7/Main3.java new file mode 100644 index 0000000..2441aaa --- /dev/null +++ b/src/main/java/de/advent_of_code_2025/day7/Main3.java @@ -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 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 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> 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 value = new ArrayList<>(); + + value.add(b); + + return value; + } + + v.add(b); + + return v; + }); + + if(row != previousRow) { + previousRow = row; + } + } + + for(Map.Entry> 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); + } +}