[–] 1 point 11 months ago*

And here's a super-simple version, because why not.

import Data.List (elemIndex, elemIndices)  
import Data.Map qualified as Map  
import Data.Maybe (fromJust)  
import Data.Set qualified as Set  

main = do  
  (start : rows) <- lines <$> readFile "input07"  
  let splitsByRow =  
        zipWith  
          ( \row beams ->  
              Set.intersection (Map.keysSet beams)  
                . Set.fromDistinctAscList  
                $ elemIndices '^' row  
          )  
          rows  
          beamsByRow  
      beamsByRow =  
        scanl  
          ( \beams splits ->  
              let unsplit = beams `Map.withoutKeys` splits  
                  split = beams `Map.restrictKeys` splits  
                  splitLeft = Map.mapKeysMonotonic pred split  
                  splitRight = Map.mapKeysMonotonic succ split  
               in Map.unionsWith (+) [unsplit, splitLeft, splitRight]  
          )  
          (Map.singleton (fromJust $ elemIndex 'S' start) 1)  
          splitsByRow  
  print . sum $ map Set.size splitsByRow  
  print . sum $ last beamsByRow  
  • source
  • parent
  • context
  • [–] 2 points 11 months ago

    There's an alternative approach to merging ranges. Put starting and ending points into a heap then scan it, keeping track of the number of open ranges present. Something like this:

    mergeRanges rs =
        let
            -- We put in this order so we count openings before closings
            starts = map (\(a, b) -> (a, -1)) rs
            ends = map (\(a, b) -> (b, 1)) rs
            heap = H.fromList (starts ++ ends) :: H.MinHeap (Int, Int)
         in
            mergeNo heap
      where
        -- not in a range currently
        mergeNo :: H.MinHeap (Int, Int) -> [R]
        mergeNo h =
            case H.view h of
                Nothing -> []
                Just ((a, -1), h') -> mergeYes a 1 h'
        -- in a range
        mergeYes :: Int -> Int -> H.MinHeap (Int, Int) -> [R]
        mergeYes start height h =
            let Just ((v, delta), h') = H.view h
                newHeight = height - delta
             in if newHeight == 0
                    then (start, v) : mergeNo h'
                    else mergeYes start newHeight h'
    
  • source
  • [–] 5 points 11 months ago* (last edited 11 months ago)

    QIR (Quantum Intermediate Representation)

    Nah, just kidding - I used Rust. The only tricky part seemed to be finding time on a Sunday to get it coded!

    Part 1 I swept down with a bool array for beams and part 2 I swept up with a score array and summed when it split (joined).

    struct Teleporter(String);
    
    impl Teleporter {
        fn new(s: String) -> Self {
            Self(s)
        }
    
        fn start_pos(line: &str) -> Result<usize> {
            line.find('S').ok_or_eyre("Start not found")
        }
    
        fn splits(&self) -> Result<usize> {
            let mut input = self.0.lines();
            let start_line = input.next().ok_or_eyre("No start line")?;
            let start = Self::start_pos(start_line)?;
    
            let mut beams = vec![false; start_line.len()];
            beams[start] = true;
    
            let mut splits = 0;
            for line in input {
                for (i, ch) in line.bytes().enumerate() {
                    if beams[i] && ch == b'^' {
                        splits += 1;
                        beams[i] = false;
                        beams[i - 1] = true;
                        beams[i + 1] = true;
                    }
                }
            }
            Ok(splits)
        }
    
        fn timelines(&self) -> Result<usize> {
            let mut input = self.0.lines();
            let start_line = input.next().ok_or_eyre("No start line")?;
            let start = Self::start_pos(start_line)?;
    
            let mut num_paths = vec![1; start_line.len()];
            for line in input.rev() {
                for (i, c) in line.bytes().enumerate() {
                    if c == b'^' || c == b'S' {
                        num_paths[i] = num_paths[i - 1] + num_paths[i + 1];
                    }
                }
            }
            Ok(num_paths[start])
        }
    }
    
  • source
  • [–] 4 points 11 months ago (1 child)

    Rust

    Dynamic programming? Nah, just keep track of the number of overlapping beams and part 2 becomes no different to part 1.

    View on github

    use std::collections::VecDeque;
    
    fn parse_input(input: &str) -> (Vec<Vec<bool>>, (usize, usize)) {
        let splits = input
            .lines()
            .map(|l| l.chars().map(|c| c == '^').collect())
            .collect();
        // Assume start is on first row
        let start = (input.chars().position(|c| c == 'S').unwrap(), 0);
        (splits, start)
    }
    
    fn solve(input: String) {
        let (splits, start) = parse_input(&input);
        let mut nsplits = 0u32;
        let mut timelines = 1u64;
        let mut frontier = VecDeque::from([(start, 1)]);
        while let Some((pos, multiplicity)) = frontier.pop_front() {
            let (x, y) = (pos.0, pos.1 + 1);
            if y == splits.len() {
                // Falls out of bottom
                continue;
            }
            if splits[y][x] {
                nsplits += 1;
                timelines += multiplicity;
                if let Some((b, m2)) = frontier.back_mut()
                    && *b == (x - 1, y)
                {
                    *m2 += multiplicity;
                } else {
                    frontier.push_back(((x - 1, y), multiplicity));
                }
                frontier.push_back(((x + 1, y), multiplicity));
            } else if let Some((b, m2)) = frontier.back_mut()
                && *b == (x, y)
            {
                *m2 += multiplicity;
            } else {
                frontier.push_back(((x, y), multiplicity));
            }
        }
        println!("Part 1: {nsplits}");
        println!("Part 2: {timelines}");
    }
    
    fn main() -> std::io::Result<()> {
        let (input, _) = util::get_input("day7.txt")?;
        solve(input);
        Ok(())
    }
    
  • source
  • [–] 3 points 11 months ago (1 child)

    Kotlin

    Tried recursive for part1, didn't work. LUCKILY for once I was smart and committed anyway, as it was the right solution for part2! I was losing my mind for a bit though as I had originally written my method with Integers...

    Solution

    class Day07 : Puzzle {
    
        val grid = mutableListOf<MutableList<Char>>()
        val partTwoCache = mutableMapOf<Pair<Int, Int>, Long>()
    
        override fun readFile() {
            val input = readInputFromFile(2025, 7, false)
            for (line in input.lines().filter { it.isNotBlank() }) {
                grid.add(line.toCharArray().toMutableList())
            }
        }
    
        override fun solvePartOne(): String {
            grid[1][grid[0].indexOf('S')] = '|'
    
            var splits = 0
            for (r in 1..<grid.size - 1) {
                for (c in 0..<grid[r].size) {
                    if (grid[r][c] == '|') {
                        if (grid[r+1][c] == '.') {
                            grid[r+1][c] = '|'
                        } else if (grid[r+1][c] == '^') {
                            grid[r+1][c-1] = '|'
                            grid[r+1][c+1] = '|'
                            splits++
                        }
                    }
                }
            }
            return splits.toString()
        }
    
        override fun solvePartTwo(): String {
            val start = grid[0].indexOf('S')
            return (1 + processBeamPartTwo(1, start)).toString() // don't forget to count the original timeline
        }
    
        private fun processBeamPartTwo(row: Int, column: Int): Long {
            if (partTwoCache.contains(Pair(row, column))) {
                return partTwoCache[Pair(row, column)]!!
            }
    
            if (row == grid.size) return 0L
            if (column == grid[row].size || column < 0) return 0L
    
            val out = if (grid[row][column] == '^') { // splitter
                1L + processBeamPartTwo(row, column - 1) + processBeamPartTwo(row, column + 1)
            } else {
                processBeamPartTwo(row + 1, column)
            }
            partTwoCache[Pair(row, column)] = out
            return out
        }
    }
    

    full code on Codeberg

  • source
  • [–] [S] 1 point 11 months ago

    Rust

    Not too difficult, but my pt1 approach didnt work for pt2. Small amount of tweaking and it was back in action. This should be a really good one for visualisations, so I am definitely going to try do something. Maybe colorise the nodes based on how many paths go through it? Definitely an animation of the trickle down. Looking forward to seeing everyone elses visualisations as well!

    spoiler

        #[test]
        fn test_y2025_day7_part2() {
            let input = include_str!("../../input/2025/day_7.txt");
            let mut rows = input
                .lines()
                .map(|l| {
                    l.chars()
                        .map(|c| match c {
                            'S' | '|' => 1isize,
                            '^' => -1isize,
                            _ => 0isize,
                        })
                        .collect::<Vec<isize>>()
                })
                .collect::<Vec<Vec<isize>>>();
            let width = rows[0].len();
            for i in 1..rows.len() {
                for j in 0..width {
                    if rows[i - 1][j] >= 1 {
                        if rows[i][j] == -1 {
                            rows[i][j - 1] += rows[i - 1][j];
                            rows[i][j + 1] += rows[i - 1][j];
                        } else {
                            rows[i][j] += rows[i - 1][j];
                        }
                    }
                }
            }
            print_tree(&rows);
            let total: isize = rows.pop().unwrap().iter().sum();
            println!("Total: {}", total);
        }
    

  • source
  • [–] 2 points 11 months ago (2 children)

    Go

    Oh my scheduling God! Part 1 was easy. Then for part 2 I started tracing each particle one by one using goroutines, but spawning billions of goroutines seemed to make my poor laptop sad. So I implemented a whole thread pool with process management and stuff, but nothing worked. So at the end I started doing the unthinkable: using my brain.

    It seems we can just reuse the same idea as part 1 one but with a clever counting scheme. Thing works and is as fast as part 1. I'm both happy and deeply sad not to have been able to leverage Go's supposed killer features - which are actually rarely useful when programming things other than servers tbh.

    Here we goooooo:

    day07.go

    package main
    
    import (
    	"aoc/utils"
    )
    
    func parseStartLine(line string) []bool {
    	runes := []rune(line)
    	beams := make([]bool, len(runes))
    
    	for idx, char := range runes {
    		if char == 'S' {
    			beams[idx] = true
    		}
    	}
    
    	return beams
    }
    
    func stepOne(input chan string) (int, error) {
    	beams := parseStartLine(<-input)
    	splitCount := 0
    
    	for line := range input {
    		runes := []rune(line)
    		for idx, char := range runes {
    			if char == '^' && beams[idx] {
    				splitCount++
    				if idx > 0 {
    					beams[idx-1] = true
    				}
    				if idx < len(runes)-1 {
    					beams[idx+1] = true
    				}
    				beams[idx] = false
    			}
    		}
    	}
    
    	return splitCount, nil
    }
    
    func valueBeams(beams []bool) []int {
    	valBeams := make([]int, len(beams))
    
    	for idx, b := range beams {
    		val := 0
    		if b {
    			val = 1
    		}
    		valBeams[idx] = val
    	}
    
    	return valBeams
    }
    
    func stepTwo(input chan string) (int, error) {
    	beams := valueBeams(parseStartLine(<-input))
    
    	for line := range input {
    		runes := []rune(line)
    		for idx, char := range runes {
    			bc := beams[idx]
    			if char == '^' && bc > 0 {
    				beams[idx] = 0
    				if idx > 0 {
    					beams[idx-1] += bc
    				}
    				if idx < len(runes)-1 {
    					beams[idx+1] += bc
    				}
    			}
    		}
    	}
    
    	sum := 0
    	for _, bc := range beams {
    		sum += bc
    	}
    
    	return sum, nil
    }
    
    func main() {
    	inputFile := utils.FilePath("day07.txt")
    	utils.RunStep(utils.ONE, inputFile, stepOne)
    	utils.RunStep(utils.TWO, inputFile, stepTwo)
    }
    
    

  • source
  • [–] 3 points 11 months ago*

    Python

    Not too difficult, especially since there are no edge cases (particles leaving the grid, adjacent splitters).

    My initial solution mutated the input for the simulation which I cleaned up after by creating an array that would record the number of particle paths at every column location + some other optimizations. I chose to implement both parts in the same function because they share the majority of the logic.

    click to view code

    def solve(data: str):
        grid = data.splitlines()
        m, n = len(grid), len(grid[0])
    
        # find the first particle
        particle_paths = [0] * n  # count of particle paths that will reach this column
        for j in range(n):
            if grid[0][j] == 'S':
                particle_paths[j] = 1
                break
        
        # count the number of splits for part 1
        splits = 0
    
        # simulate the particle moving down the grid
        #   optimization 1: we can start from the 3rd row (index 2) because that's where the first splitter is
        #   optimization 2: we can skip alternating rows because every other row is empty
        for i in range(2, m, 2):
            # particle paths per column after this row is processed
            next_particle_paths = [0] * n
    
            for j in range(n):
                if particle_paths[j] == 0:
                    # skip if there are no particle paths coming from above in this column
                    continue
                
                if grid[i][j] == '.':
                    # no splitter here, the number of paths in this column remains the same
                    # make sure to use += to account for neighboring splitters dumping additional paths into this column
                    next_particle_paths[j] += particle_paths[j]
                else:
                    # splitter activated here, any particle arriving here can end up in the left or right column
                    # this can be simulated by adding the number of paths to the columns on either side
                    splits += 1
                    next_particle_paths[j-1] += particle_paths[j]
                    next_particle_paths[j+1] += particle_paths[j]
            
            # update vars for next iteration
            particle_paths = next_particle_paths
    
        # return both 
        #   the number of splits AND 
        #   the count of timelines a particle would create
        return splits, sum(particle_paths)
    
    sample = """.......S.......
    ...............
    .......^.......
    ...............
    ......^.^......
    ...............
    .....^.^.^.....
    ...............
    ....^.^...^....
    ...............
    ...^.^...^.^...
    ...............
    ..^...^.....^..
    ...............
    .^.^.^.^.^...^.
    ..............."""
    assert solve(sample) == (21, 40)
    

  • source
  • [–] 3 points 11 months ago* (last edited 11 months ago) (1 child)

    Nim

    Another simple one.

    Part 1: count each time a beam crosses a splitter.
    Part 2: keep count of how many particles are in each column in all universes
    (e.g. with a simple 1d array), then sum.

    Runtime: 116 ΞΌs 95 Β΅s 86 Β΅s

    old version

    type
      AOCSolution[T,U] = tuple[part1: T, part2: U]
    
    proc solve(input: string): AOCSolution[int, int] =
      var beams = newSeq[int](input.find '\n')
      beams[input.find 'S'] = 1
    
      for line in input.splitLines():
        var newBeams = newSeq[int](beams.len)
        for pos, cnt in beams:
          if cnt == 0: continue
          if line[pos] == '^':
            newBeams[pos-1] += cnt
            newBeams[pos+1] += cnt
            inc result.part1
          else:
            newbeams[pos] += cnt
        beams = newBeams
      result.part2 = beams.sum()
    

    Update: found even smaller and faster version that only needs a single array.
    Update #2: small optimization

    type
      AOCSolution[T,U] = tuple[part1: T, part2: U]
    
    proc solve(input: string): AOCSolution[int, int] =
      var beams = newSeq[int](input.find '\n')
      beams[input.find 'S'] = 1
    
      for line in input.splitLines():
        for pos, c in line:
          if c == '^' and beams[pos] > 0:
            inc result.part1
            beams[pos-1] += beams[pos]
            beams[pos+1] += beams[pos]
            beams[pos] = 0
      result.part2 = beams.sum()
    

    Full solution at Codeberg: solution.nim

  • source
  • [–] 4 points 11 months ago* (last edited 11 months ago) (1 child)

    Haskell

    That was a fun little problem.

    import Data.Map qualified as Map  
    import Data.Set qualified as Set  
    import Data.Tuple (swap)  
    
    readInput s =  
      Map.fromDistinctAscList  
        [((i, j), c) | (i, l) <- zip [0 ..] $ lines s, (j, c) <- zip [0 ..] l]  
    
    beamPaths input = scanl step (Map.singleton startX 1) [startY .. endY]  
      where  
        Just (startY, startX) = lookup 'S' $ map swap $ Map.assocs input  
        Just ((endY, _), _) = Map.lookupMax input  
        step beams y =  
          Map.unionsWith (+) $  
            [ if input Map.!? (y + 1, j) == Just '^'  
                then Map.fromList [(j - 1, n), (j + 1, n)]  
                else Map.singleton j n  
              | (j, n) <- Map.assocs beams  
            ]  
    
    part1 = sum . map Set.size . (zipWith (Set.\\) <*> tail) . map Map.keysSet . beamPaths  
    
    part2 = sum . last . beamPaths  
    
    main = do  
      input <- readInput <$> readFile "input07"  
      print $ part1 input  
      print $ part2 input  
    
  • source
  • [–] 2 points 11 months ago

    Uiua

    Quite simple this one. Part 2 still takes a few seconds because I'm essentially checking off each roll individually.

    Run with example input

    Code

    $ ..@@.@@@@.
    $ @@@.@.@.@@
    $ @@@@@.@.@@
    $ @.@@@@..@.
    $ @@.@@@@.@@
    $ .@@@@@@@.@
    $ .@.@.@.@@@
    $ @.@@@.@@@@
    $ .@@@@@@@@.
    $ @.@.@@@.@.
    βŠœβˆ˜βŠΈβ‰ @\n
    =@@
    
    Rolls ← ⍣(β§»βŠšΛ™β€)∞⊸⊑1_1
    Removable ← (
      ⬚0⧈Rolls[3_3 1_1 1_1]
      βŠšβ‰€4
    )
    
    Remove ← ⍜⊑(Λœβ†―0β§»)
    
    PartOne ← β§»Removable
    
    PartTwo ← (
      βŠ™0
      β₯(
        βŠ™(βŠ™+⟜⧻)⟜Removable
        ˜Remove
      )∞
      β—Œ
    )
    &pf "Part One: "
    &p ⊸PartOne
    
    &pf "Part Two: "
    &p PartTwo
    

    Old Part 2Before seeing mykl's solution this was my solution for part 2

    PartTwoOld ← (
      0βŠ™0
      ⍒(βŠ™βŠ™β—Œ
        βŠ™βŠΈRemovable
        +βŠ™βŠΈβ§»
        βŠ™βŠΈRemove
      | Β¬β‰β—Œ)
      βŠ™β‹…β—Œ
    )
    

    It's basically the same, just that I used a while-do-loop, making the check for the ending condition myself (which took me a bit to get right because I still find loops in Uiua a bit confusing).
    Using the repeat-loop as above also gets rid of the dip's (βŠ™). I could've removed them here as well but I was already deep in the trouble of getting the loop to work correctly and I liked the little face at the beginning 0βŠ™0

  • source
  • [–] 6 points 11 months ago (4 children)

    Uiua

    I'm new to Uiua, so probably not the best way to express the solution!

    ParseRows ← (
      βŠœβˆ˜βŠΈβ‰ @\n
      βœβ‡ŒΒ°βŠ‚ # Get the row of operations.
    )
    ParseOperations ← βŠœβŠ’βŠΈβ‰ @\s
    SumCalculations ← (
      ≑(
        ⍣(β—‡/Γ—Β°@*
        | β—‡/+Β°@+
        )
      )
      /+
    )
    
    Part₁ ← (
      ParseRows
      βŠ“(ParseOperations
      | ⍉ ≑(βŠœβ‹•βŠΈβ‰ @\s) # Parse columns.
      )
      SumCalculations
    )
    
    Partβ‚‚ ← (
      ParseRows
      βŠ“(ParseOperations
      | ⊜(░≑(β‹•β–½βŠΈβ‰ @\s)) ≑/β†₯βŠΈβ‰ @\s⍉ # Parse numbers.
      )
      SumCalculations
    )
    
    &fras "6.txt"
    βŠƒPart₁ Partβ‚‚
    
  • source
  • [–] 2 points 11 months ago*

    Rust

    Finally having some fun with iters. I think part 2 came out nicely once I figured I can rotate the whole input to get numbers that look like ours.

    Q: Anyone have any tips how to reason about the intermediate types in a long iter chain? I'm currently placing dummy maps, and on occasion, even printing the values from them when I get too stuck.

    the code

    use std::fs::File;
    use std::io::{BufReader, Lines};
    
    #[allow(dead_code)]
    pub fn part1(input: Lines<BufReader<File>>) {
        let mut input = input
            .map_while(Result::ok)
            .map(|line| {
                line.split_ascii_whitespace()
                    .map(|s| s.to_string())
                    .collect::<Vec<String>>()
            })
            .collect::<Vec<Vec<String>>>();
    
        let ops = input.pop().unwrap();
        let values = input
            .iter()
            .map(|v| {
                v.iter()
                    .map(|v| v.parse::<i64>().unwrap())
                    .collect::<Vec<i64>>()
            })
            .collect::<Vec<Vec<i64>>>();
    
        let transposed: Vec<Vec<i64>> = (0..values[0].len())
            .map(|i| values.iter().map(|row| row[i]).collect())
            .collect();
    
        let mut sum = 0;
        for i in 0..ops.len() {
            let op: &str = &ops[i];
            match op {
                "+" => {
                    sum += transposed[i].iter().sum::<i64>();
                }
                "*" => {
                    sum += transposed[i].iter().product::<i64>();
                }
                _ => panic!("Invalid operation"),
            }
        }
    
        println!("sum = {}", sum)
    }
    
    #[allow(dead_code)]
    pub fn part2(input: Lines<BufReader<File>>) {
        let mut input = input
            .map_while(Result::ok)
            .map(|line| line.chars().collect::<Vec<char>>())
            .collect::<Vec<Vec<char>>>();
    
        let ops = input
            .pop()
            .unwrap()
            .iter()
            .map(|c| c.to_string())
            .filter(|s| s != " ")
            .collect::<Vec<String>>();
    
        let transposed: Vec<i64> = (0..input[0].len())
            .map(|i| input.iter().map(|row| row[i]).collect())
            .map(|vec: String| vec.trim().to_string())
            .map(|s| {
                if s.len() == 0 {
                    0
                } else {
                    s.parse::<i64>().unwrap()
                }
            })
            .collect();
    
        let groups = transposed
            .into_iter()
            .fold(Vec::new(), |mut acc: Vec<Vec<i64>>, num| {
                if num == 0 {
                    if let Some(last) = acc.last_mut() {
                        if !last.is_empty() {
                            acc.push(Vec::new());
                        }
                    } else {
                        acc.push(Vec::new());
                    }
                } else {
                    if acc.is_empty() {
                        acc.push(Vec::new());
                    }
    
                    acc.last_mut().unwrap().push(num);
                }
    
                acc
            });
    
        let mut sum = 0;
        for i in 0..ops.len() {
            let op: &str = &ops[i];
            match op {
                "+" => {
                    sum += groups[i].iter().sum::<i64>();
                }
                "*" => {
                    sum += groups[i].iter().product::<i64>();
                }
                _ => panic!("Invalid operation"),
            }
        }
    
        println!("sum = {}", sum)
    }
    
    

  • source
  • [–] 2 points 11 months ago

    Ruby

    I decided to rotate the entire input character-by-character, then parse the numbers (see the full source here)

    grid = input.lines.map(&:chomp).map {|l| l.each_char.map.to_a }.to_a
    transposed = Array.new(grid[0].length) { Array.new(grid.length) }
    
    grid.each_with_index do |row, y|
      row.each_with_index do |col, x|
        transposed[x][y] = col
      end
    end
    
    vals = []
    ops = []
    temp_vals = []
    
    transposed.each do |row|
      l = row.join("").strip
      temp_vals << l.scan(/\d+/).map(&:to_i).to_a[0]
      /[+*]/.match(l) { |m| ops << m.to_s.to_sym }
    
      if l == ""
        vals << temp_vals.compact
        temp_vals = []
      end
    end
    
    vals << temp_vals.compact unless temp_vals.empty?
    
    vals.each_with_index.sum do |v, i|
      v.inject(ops[i])
    end
    
  • source
  • [–] 3 points 11 months ago

    Haskell

    import Control.Arrow
    import Data.Char
    import Data.List
    import Text.ParserCombinators.ReadP
    
    op "*" = product
    op "+" = sum
    
    part1 s = sum $ zipWith ($) (op <$> a) (transpose $ fmap read <$> as)
      where
        (a : as) = reverse . fmap words . lines $ s
    
    parseGroups = fst . last . readP_to_S (sepBy (endBy int eol) eol) . filter (/= ' ')
      where
        eol = char '\n'
        int = read <$> munch1 isDigit :: ReadP Int
    
    part2 s = sum $ zipWith ($) (op <$> words a) (parseGroups . unlines $ reverse <$> transpose as)
      where
        (a : as) = reverse $ lines s
    
    main = getContents >>= print . (part1 &&& part2)
    
  • source
  • view more: β€Ή prev next β€Ί