copy pasting the rules from last year’s thread:

Rules: no spoilers.

The other rules are made up aswe go along.

Share code by link to a forge, home page, pastebin (Eric Wastl has one here) or code section in a comment.

  • zogwarg@awful.systems
    link
    fedilink
    English
    arrow-up
    4
    ·
    20 days ago

    Day 8

    Al lot of grid index shuffling these past few days! Not too difficult yet though, will this year be gentler or much harsher later?

    Part 2 code in JQ
    #!/usr/bin/env jq -n -R -f
    
    [ inputs / "" ] | [.,.[0]|length] as [$H,$W] |
    
    #----- In bound selectors -----#
    def x: select(. >= 0 and . < $W);
    def y: select(. >= 0 and . < $H);
    
    reduce (
      [
        to_entries[] | .key as $y | .value |
        to_entries[] | .key as $x | .value |
        [ [$x,$y],. ]  | select(last!=".")
      ] | group_by(last)[] # Every antenna pair #
        | combinations(2)  | select(first < last)
    ) as [[[$ax,$ay]],[[$bx,$by]]] ({};
      # Assign linear anti-nodes #
      .[ range(-$H;$H) as $i | "\(
        [($ax+$i*($ax-$bx)|x), ($ay+$i*($ay-$by)|y)] | select(length==2)
      )"] = true
    ) | length
    
  • swlabr@awful.systems
    link
    fedilink
    English
    arrow-up
    3
    ·
    18 days ago

    Day 10. I lied about doing this later, I guess.

    p1, 2 I accidentally solved 2. before 1.

    My initial code was: for every 9, mark that square with a score of 1. Then: for (I = 8, then 7 … 0) => mark the square with the sum of the scores of the squares around it with a value of i + 1.

    Except that gives you all the ways to reach 9s from a 0, which is part 2. For part 1, I changed the scores to be sets of reachable 9s, and the score of a square was the size of the set at that position.

    • Architeuthis@awful.systemsOP
      link
      fedilink
      English
      arrow-up
      2
      ·
      18 days ago
      10 commentary

      Yeah basically if you were doing DFS and forgot to check if you’d already visited the next node you were solving for pt2, since the rule about the next node always having a value of +1 compared to the current one was already preventing cyclic paths.

      10 Code

      Hardly a groundbreaking implementation but I hadn’t posted actual code in a while so

      (* F# - file reading code and other boilerplate omited *)
      
      let mapAllTrails (matrix : int array2d) =
          let rowCount = matrix |> Array2D.length1
          let colCount = matrix |> Array2D.length2
      
          let rec search (current:int*int) (visited: HashSet<int*int>) (path: (int*int) list) : (int*int) list list= 
              let (row,col) = current
              let currentValue = matrix.[row,col]
      
              // Remove to solve for 10-2
              visited.Add (row,col) |> ignore
      
              // If on a 9 return the complete path
              if currentValue = 9 then [List.append path [row,col] ]
              // Otherwise filter for eligible neihboring cells and continue search
              else                    
                  [ row-1, col;row, col-1; row, col+1; row+1,col]
                  |> List.filter (fun (r,c) -> 
                      not (visited.Contains(r,c))
                      && r >= 0 && c>=0 && r < rowCount && c < colCount
                      && matrix.[r,c]-currentValue = 1 )
                  |> List.collect (fun next ->
                      [row,col] 
                      |> List.append path  
                      |> search next visited)
      
          // Find starting cells, i.e. contain 0
          matrix
          |> Global.matrixIndices
          |> Seq.filter (fun (row,col) -> matrix.[row,col] = 0)
          // Find all trails starting from those cells and flatten the result
          |> Seq.collect (fun trailhead -> search trailhead (HashSet<int*int>()) [])
          
      
      "./input.example"
      |> Common.parse 
      |> mapAllTrails
      |> Seq.length
      |> Global.shouldBe 81
      
      "./input.actual"
      |> Common.parse 
      |> mapAllTrails
      |> Seq.length
      |> printfn "The sum total of trail rankings is %d"
      
        • zogwarg@awful.systems
          link
          fedilink
          English
          arrow-up
          1
          ·
          17 days ago
          re:10

          Mwahaha I’m just lazy and did are “unique” (single word dropped for part 2) of start/end pairs.

          #!/usr/bin/env jq -n -R -f
          
          ([
               inputs/ "" | map(tonumber? // -1) | to_entries
           ] | to_entries | map( # '.' = -1 for handling examples #
               .key as $y | .value[]
             | .key as $x | .value   | { "\([$x,$y])":[[$x,$y],.] }
          )|add) as $grid | #           Get indexed grid          #
          
          [
            ($grid[]|select(last==0)) | [.] |    #   Start from every '0' head
            recurse(                             #
              .[-1][1] as $l |                   # Get altitude of current trail
              (                                  #
                .[-1][0]                         #
                | ( .[0] = (.[0] + (1,-1)) ),    #
                  ( .[1] = (.[1] + (1,-1)) )     #
              ) as $np |                         #   Get all possible +1 steps
              if $grid["\($np)"][1] != $l + 1 then
                empty                            #     Drop path if invalid
              else                               #
              . += [ $grid["\($np)"] ]           #     Build path if valid
              end                                #
            ) | select(last[1]==9)               #   Only keep complete trails
              | . |= [first,last]                #      Only Keep start/end
          ]
          
          # Get score = sum of unique start/end pairs.
          | group_by(first) | map(unique|length) | add
          
      • Architeuthis@awful.systemsOP
        link
        fedilink
        English
        arrow-up
        2
        ·
        edit-2
        19 days ago

        I almost got done in by floating point arithmetic, I think

        8-2 commentary

        Used the coordinates of every two same type frequences to create the ilnear equation (y = ax + b) and then fed it all the matrix coordinates to see which belonged to the line. To get the correct number of antinodes I had to check for |y - ax - b| < 0.0001, otherwise I got around 20 too few.

  • swlabr@awful.systems
    link
    fedilink
    English
    arrow-up
    2
    ·
    18 days ago

    I will probably slow down and try to solve the problems on the weekends rather than daily. Anyway, 9:

    part 1

    This was straightforward in that all you need to do is implement the algorithm as given. I did optimise a little using the arithmetic progression sum, but this is a speed-up of, at most like, a factor of 9.

    I am pretty sure I did an OK job at avoiding edge cases, though I suspect this problem has many of them.

    part 2

    Again, the algorithm is more or less given: Start from the back, look for a hole that’ll work, and put it in if you can. Otherwise, don’t. Then, calculate the contribution to the checksum.

    The complex part was the “look for a hole” part. My input size was 19999, meaning an O(n2) solution was probably fast enough, but I decided to optimise and use a min heap for each space size prematurely. I foresaw that you need to split up a space if it is oversized for a particular piece of data, i.e. pop the slot from the heap, reduce the size of the slot, and put it in the heap corresponding to the new size.

    • gerikson@awful.systems
      link
      fedilink
      English
      arrow-up
      3
      ·
      edit-2
      18 days ago

      I had a lot of trouble because my input was truncated, despite the site warning me about that. Did I listen? I did not.

      edit

      day 9 discussion

      Principal Skinner moment

      “Did I miscopy the input?”

      “No, it is my algorithm that is wrong”

      kinda satisfying to figure out I was correct all along.

      Part 2 is not as fast as I’d like (14s), but faster than it was. People on reddit are wittering on about search trees, me I just sling Perl hashrefs around

      https://github.com/gustafe/aoc2024/blob/main/d09-Disk-Fragmenter.pl

      • zogwarg@awful.systems
        link
        fedilink
        English
        arrow-up
        1
        ·
        edit-2
        18 days ago
        Day 9 discussion

        Part two for me was also very slow until I, speed up the index search by providing a lower bound for the insertion. for every insertion of size “N”, I have an array lower = [null, 12, 36, …], since from the left any time you find free space for a given size, the next time must be at an index at least one larger, which makes it close to being O(N) [assuming search for the next free space is more or less constant] instead of O(N^2), went from about 30s to 2s. https://github.com/zogwarg/advent-of-code/blob/main/2024/jq/09-b.jq

  • Sailor Sega Saturn@awful.systems
    link
    fedilink
    English
    arrow-up
    1
    ·
    edit-2
    22 days ago

    OK nerds, I was coerced into doing day five so I’m posting it here.

    spoiler

    stable sort with the ordering critera as the sort condition and it just works, either that or I got lucky inputs

    5-1 / 5-2
    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <sstream>
    #include <string>
    #include <unordered_map>
    
    std::unordered_multimap<int, int> ordering;
    
    bool sorted_before(int a, int b) {
      auto range = ordering.equal_range(a);
      for (auto it = range.first; it != range.second; ++it) {
        if (it->second == b) return true;
      }
      return false;
    }
    
    int main() {
      int sum = 0;
      std::string line;
      while (std::getline(std::cin, line) && !line.empty()) {
        int l, r;
        char c;
        std::istringstream iss(line);
        iss >> l >> c >> r;
        ordering.insert(std::make_pair(l,r));
      }
      while (std::getline(std::cin, line)) {
        std::istringstream iss(line);
        std::vector<int> pages;
        int page;
        while (iss >> page) {
          pages.push_back(page);
          iss.get();
        }   
        std::vector<int> sorted_pages = pages;
        std::stable_sort(sorted_pages.begin(), sorted_pages.end(), sorted_before);
        if (pages == sorted_pages) {  // Change to != for 5-2
          sum += sorted_pages[sorted_pages.size()/2];
        }
      }
      std::cout << "Sum: " << sum << std::endl;
    }
    
    • Architeuthis@awful.systemsOP
      link
      fedilink
      English
      arrow-up
      1
      ·
      edit-2
      22 days ago

      tl;dr: Day 5 was most perfectly fine code thrown out for me, because I ran face first into eliminating imaginary edge cases instead of starting out simple.

      5-1 commentary

      I went straight into a rabbit hole of doing graph traversal to find all implicit rules (i.e. 1|2, 2|3, 3|4 imply 1|3, 1|4, 2|4) so I could validate updates by making sure all consequent pairs appear in the expanded ruleset. Basically I would depth first search a tree with page numbers for nodes and rules for edges, to get all branches and recombine them to get the full ruleset.

      So ideally 1|2, 2|3, 3|4 -> 1|2|3|4 -> 1|2, 2|3, 3|4, 1|3, 1|4, 2|4

      Except I forgot the last part and just returned the branch elements pairwise in sequence, which is just the original rules, which I noticed accidentally after the fact since I was getting correct results, because apparently it was completely unnecessary and I was just overthinking myself into several corners at the same time.

      5-2 commentary and some code

      The obvious cornerstone was the comparison function to reorder the invalid updates, this is what I came up with:

      let comparerFactory (ruleset: (int*int) list) :int -> int -> int = 
          let leftIndex = 
              ruleset 
              |> List.groupBy fst 
              |> List.map (fun (key,grp)-> key, grp |> List.map snd)
              |> Map.ofList
      
          fun page1 page2 -> 
              match (leftIndex  |> Map.tryFind page1) with
              | Some afterSet when afterSet |> List.contains page2 -> -1
              | _ -> 1
      

      The memoization pattern is for caching an index of rules grouped by the before page, so I can sort according to where each part of the comparison appears. I started out with having a second index where the key was the ‘after’ page of the rule which I would check if the page didn’t appear on the left side of any rule, but it turned out I could just return the opposite case, so again unnecessary.

  • Mii@awful.systems
    link
    fedilink
    English
    arrow-up
    0
    ·
    24 days ago

    Advent of Code is one of these things I wanna do every year and then I end up in fucking end-of-the-year crunch time every December and work for 10-12 hours and really don’t wanna code after work anymore.

    But hey, here’s a quick solution for day 1. Let’s see how far I make it.

    Day 1
    use strict;
    use List::Util qw( min max );
    
    open(FH, '<', $ARGV[0]) or die $!;
    
    my @left;
    my @right;
    
    while (<FH>) {
    	my @nums = split /\s+/, $_;
    	push(@left, $nums[0]);
    	push(@right, $nums[1]);
    }
    
    @left = sort { $b <=> $a } @left;
    @right = sort { $b <=> $a } @right;
    
    my $dist = 0;
    my $sim = 0;
    my $i = 0;
    
    foreach my $lnum (@left) {
    	$sim += $lnum * grep { $_ == $lnum } @right;
    
    	my $rnum = $right[$i++];
    	$dist += max($lnum, $rnum) - min($lnum, $rnum);
    }
    
    print 'Part 1: ', $dist, "\n";
    print 'Part 2: ', $sim, "\n";
    
    close(FH);
    
    • Mii@awful.systems
      link
      fedilink
      English
      arrow-up
      1
      ·
      17 days ago

      Yay, day 3 with Regexp magic.

      Day 3
      open(FH, '<', $ARGV[0]) or die $!;
      my $sum = 0;
      my $sum2 = 0;
      
      my $enabled = 1;
      
      while (<FH>) {
          while ($_ =~ /(?:mul\((\d{1,3}),(\d{1,3})\)|(do)\(\)|(don\'t)\(\))/g) {
              $enabled = 1 if $3;
              $enabled = 0 if $4;
              $sum += $1 * $2 if $1 && $2;
              $sum2 += $1 * $2 if $enabled && $1 && $2;
          }
      }
      
      close(FH);
      
      print "Part 1: $sum\n";
      print "Part 2: $sum2\n";
      
  • swlabr@awful.systems
    link
    fedilink
    English
    arrow-up
    0
    ·
    edit-2
    21 days ago

    Day 7

    1 and 2

    On reflection, it was a pretty fun little problem to solve. There wasn’t much of a difference between the two parts. I ran into some bugs with my recursion termination conditions, but I got them in the end.

    Part 1. A quick look at the data showed that the input length was short enough to perform an O(2n) search with some early exits. I coded it as a dfs.

    Part 2. Adding concatenation just changes the base from 2 to 3, which, while strictly slower, wasn’t much slower for this input.

    code
    void d7(bool sub) => print(getLines()
        .map((l) => l.split(RegExp(r':? ')).map(int.parse).toList())
        .fold<int>(
            0, (p, ops) => test(ops, ops[0], ops[1], 2, sub) ? ops[0] + p : p));
    
    bool test(List<int> l, int cal, int cur, int i, bool sub) =>
        cur == cal && i == l.length ||
        (i < l.length && cur <= cal) &&
            (test(l, cal, cur + l[i], i + 1, sub) ||
                test(l, cal, cur * l[i], i + 1, sub) ||
                (sub && test(l, cal, cur.concat(l[i]), i + 1, sub)));
    
    • gerikson@awful.systems
      link
      fedilink
      English
      arrow-up
      2
      ·
      edit-2
      21 days ago
      Re: day 7 parts 1 and 2

      same here, I was dicking around with combinatorics to get all combos of plus and multiply but realized before I got to the end it was gonna take too long. Then I figured that a DFS was the way to go.

      I tried to optimize a bit by exiting early if the cumulative result became too large, but for some reason that gave me incorrect (too low) answers. Part 2 runs in around 1 min anyway.

      https://github.com/gustafe/aoc2024/blob/main/d07-Bridge-Repair.pl

      • Architeuthis@awful.systemsOP
        link
        fedilink
        English
        arrow-up
        3
        ·
        edit-2
        20 days ago

        My graph search solves 7-1 and passes the example cases for 7-2, but gives too low a result for the complete puzzle input, and there’s no way I’m manually going through every case to find the false negative. On to day 8 I guess.

        7-2 Check case by simple graph search that mostly works
        // F#
        let isLegit ((total: int64), (calibration : int64 array)) = 
        
            let rec search (index : int) (acc: int64) =
                let currentValue = calibration.[index]
                
                [Add; Times; Concat] // operators - remove 'Concat' to solve for 7-1
                |> List.exists (fun op -> // List.exists returns true the first time the lambda returns true, so search stops at first true
                        match op with // update accumulator
                        | Add -> acc + currentValue
                        | Times -> acc * currentValue
                        | Concat -> int64 (sprintf "%d%d" acc currentValue)
                        |> function // stop search on current accumulator value (state) exceeding total, or being just right
                        | state when state > total -> false
                        | state when state = total && index < (calibration.Length-1) -> false // this was the problem
                        | state when state = total && index = (calibration.Length-1) -> true
                        | state -> // stop if index exceeds input length, or continue search
                            if index+1 = calibration.Length
                            then false
                            else search (index+1) state
                )
             
            // start search from second element using the first as current sum
            search 1 calibration.[0]
        

        EDIT: total && index < (calibration.Length-1) -> false – i.e. stop if you reach the total before using all numbers, well, also stops you from checking the next operator, So, removing it worked.

        Rubber ducking innocent people on the internets works, who knew.