Rules: no spoilers.

The other rules are made up as we go along.

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

  • gerikson@awful.systemsOP
    link
    fedilink
    English
    arrow-up
    4
    ·
    1 year ago

    Day 14: Parabolic Reflector Dish

    I only managed part 1 today. My enthusiasm for index fiddling is waning rapidly.

    • zogwarg@awful.systems
      link
      fedilink
      English
      arrow-up
      4
      ·
      1 year ago

      How about not fiddling with indices?

      JQ Notfiddlingwithindexification

      https://github.com/zogwarg/advent-of-code/blob/main/2023/jq/14-a.jq

      #!/usr/bin/env jq -n -R -f
      
      # Dish to grid
      [ inputs / "" ]
      
      # Tilt UP
      | transpose                       # Transpose, for easier RE use
      | map(                            #
        ("#" + add) | [                 # For each column,   replace '^' with '#'
          scan("#[O.]*") | [            # From '#' get empty spaces and 'O' rocks
            "#", scan("O"), scan("\\.") # Let gravity do it's work.
          ]                             #
        ] | add[1:]                     # Add groups back together
       )                                #
      | transpose                       # Transpose back
      
      # For each row, count  'O'  rocks
      | map(add | [scan("O")] | length)
      
      # Add total load on "N" beam
      | [0] + reverse | to_entries
      | map( .key * .value ) | add
      

      Similarly tired with index fiddling, I was pretty happy with my approach, which led to satisfying transpose cancelling in part 2. Not the fastest code out there, but it works. Day 14 was actually my favorite one so far ^^.

  • swlabr@awful.systems
    link
    fedilink
    English
    arrow-up
    3
    ·
    edit-2
    11 months ago

    21 Step Counter

    Starting this thread having only solved a.

    A

    Pretty straightforward. Could probably be done in a few lines with the right syntactic sugar.

    B

    This is some game of life thing that I’ve never implemented or seen an implementation of, so I am altogether lost.

    My current code (https://github.com/Fluxward/aoc2023/blob/main/21.dart) has a memoisation based approach but my current ailments are preventing me from really thinking about this properly so I am bowing out until I have the wherewithal.

    • gerikson@awful.systemsOP
      link
      fedilink
      English
      arrow-up
      3
      ·
      11 months ago

      This is the hardest problem of the year so far, based on leaderboard completion times. I’m busy wrapping up work for this year, and looking for a new job, so this will have to be put on the TODO pile

      • swlabr@awful.systems
        link
        fedilink
        English
        arrow-up
        2
        ·
        11 months ago

        At this point I have officially given up and started looking at other people’s code. I’ll work on it after Imm fully better, it’s too much for me right now.

    • zogwarg@awful.systems
      link
      fedilink
      English
      arrow-up
      3
      ·
      11 months ago

      Only solved by receving heavy hints from other’s solution, and it still took me forever. By far the hardest this year.

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      2
      ·
      11 months ago

      Update on B:

      still no solve, however

      Through glancing at someone else’s code, I was inspired to just try simulating the A problem beyond 64 steps and seeing the result.

      Essentially it reaches a (bi stable?) steady state between two numbers, which makes sense- if you can only make single rook steps, then the reachable squares will alternate every cycle.

      Don’t know if I’ll try solve this again tonight but mentally I have now understood the solution.

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      1
      ·
      edit-2
      11 months ago

      Update to the update: now fully recovered, I am now trying to finish the last problems.

      Solved 21 B!

      I spent way too much time on this but it’s fine

      So my approach to AOC has always been to write a pure coding solution, which finally broke down here.

      First, the solve:

      I call the unrepeated garden map the “plot”. Each repetition of the plot I call a “grid”. Hope that isn’t confusing.

      1. Looking at the input data, it is a grid of 131x131 squares with the starting position in the center at 66,66 (indexed from 1)
      2. If you imagine a chessboard pattern over the whole area, on each step the possible reachable squares alternate colors.
      3. Row 66 and col 66 are unimpeded by stones, as well as the edges of each grid. This means that:
      • starting from the initial grid, it takes 66 steps to enter a new grid. You enter a new grid on all sides.
      • a grid is always initially entered from the middle of an edge, either on one or two sides based on its position relative to the grids that are currently being considered.
      • each grid is independent of every other grid, except for the step where it is entered.

      To see why that last point is true, consider that in order for another grid A to influence an adjacent grid B beyond the moment the adjacent grid is entered, there must be a reachable point further from the midpoint of the edge on the edge of A. However, because the middle row and column are free from rocks, this is never the case. Any influence from A reaches B too late, i.e. reachable squares on B from A will be reachable sooner from just travelling from the entry point on B.

      1. The number of steps is 131x100x2023 + 65.

      So putting all this together, the way I got the answer was like this:

      1. Simulate the whole area for 65 + (5*131) steps (more than necessary)
      2. Print the number of reachable squares in the grid at 65 + 131n for n = 0-5
      3. Using pen and paper, determine some coefficients for some of the numbers that come up, add everything up in a calculator, and type that answer into AOC.

      So I guess the answer I arrived at was what I’d been thinking I should be doing this whole time: a mix of simulating some of the problem and a decent amount of pen and paper work to get the solution out, rather than just pure coding. Fun!

  • swlabr@awful.systems
    link
    fedilink
    English
    arrow-up
    3
    ·
    11 months ago

    Sorry for the necropost: I have completed all the problems! One of them completely stumped me and I had to cheat. Not going to do a writeup unless requested :)

    • gerikson@awful.systemsOP
      link
      fedilink
      English
      arrow-up
      3
      ·
      11 months ago

      congrats! I have officially checked out of the competition for the time being. Maybe if I get some spare energy later.

      What problem had you stumped?

      • swlabr@awful.systems
        link
        fedilink
        English
        arrow-up
        3
        ·
        11 months ago

        24b, sort of. The problem came down to “hey do you remember how to do linear algebra?” and the answer was: dawg, I barely know normal algebra. I had solved the vibes of the problem but none of the details, though.

  • swlabr@awful.systems
    link
    fedilink
    English
    arrow-up
    3
    ·
    edit-2
    11 months ago

    Day 20: Pulse Propagation

    It feels weird to kick one of these threads off, but hey, here we go.

    Code as always: https://github.com/Fluxward/aoc2023/blob/main/20.dart

    a,b

    A

    So following from yesterday where I was punished by not going full OO, I decided, hey, this is a problem in which I can do some OOP, so I did. This took very long to do but I regret nothing. If you look at my code, feel free to click your tongue and shake your head at every opportunity I missed to use a design pattern.

    Anyway, after a slight snafu with misunderstanding the FF logic and not spotting that some modules can be dummy modules, it all just worked, and I got my answer.

    B

    This was a bit of a headscratcher, but the answer was surprisingly simple.

    First, the answer. Here’s how to do it:

    • Look for the “rx” module in your input.
    • If the module that outputs to rx is a conjunction, keep track of how many button presses it takes for each input of the conjunction to change. The answer is the LCM of all those numbers.
    • If the module is a FF, you can also work backwards to get it, but this was not my input so I didn’t need to try this.

    Getting here was a bit weird. I hoped that I could just run the code from A and spit out the answer when rx went low, but as of time of writing I’ve been running it now on a separate machine for about an hour and still no result.

    My next instinct was to simply work it out from pen and paper. I thought it might be possible (it probably is) but decided to at least experimentally see if the states of the modules connected to rx were cyclic first. I did, and that was enough for me to get to the answer.

    My answer was about 230 trillion BPs, which, extrapolating on how long execution is taking on my other machine, might take just under 137 years to calculate naively. Fun!

    • gerikson@awful.systemsOP
      link
      fedilink
      English
      arrow-up
      4
      ·
      11 months ago

      I’m having a hard time modeling the network at the moment, there’s too much shit to keep track of. Objects might be the solution!

      • swlabr@awful.systems
        link
        fedilink
        English
        arrow-up
        4
        ·
        11 months ago

        Yeah, I mean it’s always possible to do without all the OO stuff, I just didn’t want to mentally keep tabs on what each variable or method did what. That’s what the code is for!

  • swlabr@awful.systems
    link
    fedilink
    English
    arrow-up
    3
    ·
    11 months ago

    Happy Holidays everyone. I’ve decided I am going to take a break from aoc to properly rest and recover from my mystery illness. Perhaps I will attempt solves again in the new year.

    • gerikson@awful.systemsOP
      link
      fedilink
      English
      arrow-up
      3
      ·
      11 months ago

      Happy holidays to you too! I decided this morning that I’m not gonna work myself up missing days, so they are on hold until after xmas for me!

      Get well soon!

      • swlabr@awful.systems
        link
        fedilink
        English
        arrow-up
        2
        ·
        11 months ago

        Thanks dawg. AOC has occupied the working part of my brain but now that it requires more brain wrinklies than usual I’m gonna go back to writing sneers.

  • zogwarg@awful.systems
    link
    fedilink
    English
    arrow-up
    2
    ·
    edit-2
    1 year ago

    Day 12: Hot springs

    https://adventofcode.com/2023/day/12

    • Leaderboard completion time: 22:57
    • Personal completion time: ahahahahahahaha (at least i had fun)

    Where a curse the fact I decided to use JQ and not a “real” programming language.

    spoiler

    Had to resort to memoization, but sadly JQ isn’t mega well suited to that. I had to refactor my part 1 function, to make including the “state” at every function call possible. I wish it were as easy as a @cache decorator, but i guess this way i had fun (for an arbitrary definition of “fun”)

    Further cleaned up version: https://github.com/zogwarg/advent-of-code/blob/main/2023/jq/12-b.jq

    Also lost a fair amount of time not not noticing that the sequence should be joined with "?" not with "". (that’ll teach me to always run on the example before the full input, when execution time is super long).

    Execution time: 17m10s (without memoization a single row was taking multiple minutes, and there’s 1000 rows ^^…)

    EDIT: see massive improvement by running in parallel in reply.

    • zogwarg@awful.systems
      link
      fedilink
      English
      arrow-up
      3
      ·
      1 year ago

      A nice workaround to jq single threadedness, since this is maq reduce and safe to parallelize. 17m10s -> 20s !!!

      Spoiler link to commit.

      https://github.com/zogwarg/advent-of-code/commit/fef153411fe0bfe0e7d5f2d07da80bcaa18c952c

      Not really spoilery details: Revolves around spawing mutiple jq instances and filtering the inputs bassed on a modulo of number of instances:

        # Option to run in parallel using xargs
        # Eg: ( seq 0 9 | \
        #        xargs -P 10 -n 1 ./2023/jq/12-b.jq input.txt --argjson s 10 --argjson i \
        #      ) | jq -s add
        # Execution time 17m10s -> 20s
        if $ARGS.named.s and $ARGS.named.i then #
          [inputs] | to_entries[] | select(.key % $ARGS.named.s == $ARGS.named.i) | .value / " "
        else
          inputs / " "
        end
      

      I use JQ at work, and never really needed this, i guess this trick is nice to have under the belt just in case.

  • zogwarg@awful.systems
    link
    fedilink
    English
    arrow-up
    2
    ·
    edit-2
    11 months ago

    Day 18: Lavaduct Lagoon

    [Language: jq]

    https://github.com/zogwarg/advent-of-code/blob/main/2023/jq/18-b.jq

    Satisfyingly short (in lines, not in time writing) some of the longer part is hexadecimal parsing, that doesn’t come natively in JQ, I started doing polygon math from part 1, and what took me the longest was properly handling the area contributed by the perimeter. (I toyed with trying very annoying things like computing the outmost vertex at each turn, which is complicated by the fact that you don’t initially know which way the digger is turning, and needing previous and next point to disambiguate).

    #!/usr/bin/env jq -n -R -f
    
    reduce (
      # Produce stream of the vertices, for the position of the center
      foreach (
        # From hexadecimal representation
        # Get inputs as stream of directions = ["R", 5]
        inputs | scan("#(.+)\\)") | .[0] / ""
        | map(
            if tonumber? // false then tonumber
            else {"a":10,"b":11,"c":12,"d":13,"e":14,"f":15}[.] end
          )
        | [["R","D","L","U"][.[-1]], .[:-1]]
        | .[1] |= (
          # Convert base-16 array to numeric value.
          .[0] * pow(16;4) +
          .[1] * pow(16;3) +
          .[2] * pow(16;2) +
          .[3] * 16 +
          .[4]
        )
      ) as $dir ([0,0];
        if   $dir[0] == "R" then .[0] += $dir[1]
        elif $dir[0] == "D" then .[1] += $dir[1]
        elif $dir[0] == "L" then .[0] -= $dir[1]
        elif $dir[0] == "U" then .[1] -= $dir[1]
        end
      )
      # Add up total area enclosed by path of center
      # And up the are of the perimeter, perimeter * 1/2 + 1
    ) as [$x, $y] ( #
      {prev: [0,0], area: 0, perimeter_area: 1  };
    
      # Adds positve rectangles
      # Removes negative rectangles
      .area += ( $x - .prev[0] ) * $y |
    
      # Either Δx or Δy is 0, so this is safe
      .perimeter_area += (($x - .prev[0]) + ($y - .prev[1]) | abs) / 2 |
    
      # Keep current position for next vertex
      .prev = [$x, $y]
    )
    
    # Output total area
    | ( .area | abs ) + .perimeter_area
    

    Day 19: Aplenty

    [Language: jq]

    https://github.com/zogwarg/advent-of-code/blob/main/2023/jq/19-b.jq

    Satisfyingly very well suited to JQ once you are used to the stream, foreach(init; mod; extract) and recurse(exp) [where every output item of exp as a stream is fed back into recurse] operators. It’s a different way of coding but has a certain elegance IMO. This was actually quick to implement, along with re-using the treating a range as a primitive approach of the seeds-to-soil day.

    #!/usr/bin/env jq -n -sR -f
    
    inputs / "\n\n"
    
    # Parse rules
    | .[0] / "\n"
    | .[] |= (
      scan("(.+){(.+)}")
      | .[1] |= (. / ",")
      | .[1][] |= capture("^((?<reg>.)(?<op>[^\\d]+)(?<num>\\d+):)?(?<to>[a-zA-Z]+)$")
      | ( .[1][].num | strings ) |= tonumber
      | {key: .[0], value: (.[1]) }
    ) | from_entries as $rules |
    
    # Split part ranges into new ranges
    def split_parts($part; $rule_seq):
      # For each rule in the sequence
      foreach $rule_seq[] as $r (
        # INIT = full range
        {f:$part};
    
        # OPERATE =
        # Adjust parts being sent forward to next rule
        if $r.reg == null then
          .out = [ .f , $r.to ]
        elif $r.op == "<" and .f[$r.reg][0] < $r.num then
          ([ .f[$r.reg][1], $r.num - 1] | min ) as $split |
          .out = [(.f | .[$r.reg][1] |= $split ), $r.to ] |
          .f[$r.reg][0] |= ($split + 1)
        elif $r.op == ">" and .f[$r.reg][1] > $r.num then
          ([ .f[$r.reg][0], $r.num + 1] | max ) as $split |
          .out = [(.f | .[$r.reg][0] |= $split), $r.to ]  |
          .f[$r.reg][1] |= ($split - 1)
        end;
    
        # EXTRACT = parts sent to other nodes
        # for recursion call
        .out | select(all(.[0][]; .[0] < .[1]))
      )
    ;
    
    [ # Start with full range of possible sings in input = "in"
      [ {x:[1,4000],m:[1,4000],a:[1,4000],s:[1,4000]} , "in" ] |
    
      # Recusively split musical parts, into new ranges objects
      recurse(
        if .[1] == "R" or .[1] == "A" then
          # Stop recursion if "Rejected" or "Accepted"
          empty
        else
          # Recursively split
          split_parts(.[0];$rules[.[1]])
        end
        # Keep only part ranges in "Accepted" state
      ) | select(.[1] == "A") | .[0]
    
      # Total number if parts in each object is the product of the ranges
      | ( 1 + .x[1] - .x[0] ) *
        ( 1 + .m[1] - .m[0] ) *
        ( 1 + .a[1] - .a[0] ) *
        ( 1 + .s[1] - .s[0] )
      # Sum total number of possibly accepted musical parts
    ] | add
    

    EDIT: Less-thans and greater-thans replaced by fullwidth version, because lemmy is a hungry little goblin.

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      3
      ·
      11 months ago
      Nice!

      Also, kudos for working with polygon area from the start. I was too invested in reusing my code as discussed elsewhere, but I came around in the end.

  • zogwarg@awful.systems
    link
    fedilink
    English
    arrow-up
    2
    ·
    11 months ago

    Day 17: Clumsy Crucible

    Intimidating at first, so I went shopping for christmas presents first, which engaged my brain juices.

    [Language: jq]

    https://github.com/zogwarg/advent-of-code/blob/main/2023/jq/17-b.jq

    In hindsight this is is searching for the shortest path in a graph, making sure that each square is actually two nodes, for when approached vertically or horizontally. My shcool days are distant now, and i wonder how far from optimal my implementation is ^^.

    Part two was only a small edit from part one for me, and the code actually ran faster! from ~45s -> ~20s.

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      4
      ·
      11 months ago
      a,b

      a: Just copied what I did for 10 b. and applied it to work here. Had to fiddle with it to make it work, but it worked. I implemented a line-crossing counting algorithm. If you slice up the area into rows, you can tell if you are inside the area if you cross an odd number of lines that vertically cross that row.

      b. Pretty much just changed the input parsing and ran the same algorithm. Thought that it might be too slow, but I put in some stopwatch ticks and found out that it should take about 5 minutes to run my a) algorithm to get the answer.

      The actual time elapsed was 5m50s.

      I miiiiight try make a faster version but my head is too fucked up to care right now, hence me letting the slow algorithm run.

      • swlabr@awful.systems
        link
        fedilink
        English
        arrow-up
        4
        ·
        11 months ago

        Update on 18.

        It's not cheating if it's something you learned and forgot from a university course

        So, I have made my code a million times faster. My original algorithm counted every square of area individually, plus a bunch of other inefficient things.

        My new algorithm now uses some swanky computational geometry to calculate the enclosed area. It took a bit of scribbling on a paper pad to get this right. Some notes:

        1. If you naively use the coordinates you land on as the points of a polygon and then calculate the area of that polygon, you will slightly underestimate the enclosed area.
        2. The error comes from the contribution of the area of the perimeter. Drawing out the sample case and observing how it contributes, it basically contributes 1/2 m^3 for every straight portion of the perimeter, and 1/4 or 3/4 for a corner, depending on if that corner is a right or left turn. It should instead contribute 1 for each perimeter tile, so you need to calculate this difference.
        3. You can use the cross product to determine if a set of 3 points forms a straight line, a left turn, or a right turn.

        So, here’s what I did. I looked at some old lecture notes I had on programming competition computation geometry implementation details. I copied and pasted some code that implemented a counter-clockwise check and one that calculated the area of a simple polygon. That’s pretty much all I needed.

        Now my code runs in a few milliseconds.

        There is almost definitely a more elegant way to do what I did for this iteration but I’m happy to leave that to someone else to figure out (or copy paste from reddit or something).

        • zogwarg@awful.systems
          link
          fedilink
          English
          arrow-up
          3
          ·
          11 months ago
          18

          The beauty is you don’t need to keep track of the corners at all: ultimately the area contributed by the perimeter is ( 1/2 * perimeter ) + 1. The short justification is that is if was just ( 1/2 * perimeter ), for every inside corners you overcount by 1/4 and for every outside corner you undercount. And there is exactly 4 more outside corners that inside ones, always. You can justify that by having an arrow follow the eddges, utlmately the arrow must make 1 full turn, each outside corner adds 1/4 turn. each inside corner removes 1/4 turn.

          • swlabr@awful.systems
            link
            fedilink
            English
            arrow-up
            2
            ·
            edit-2
            11 months ago

            I knew there was a better way! Thanks!

            perhaps

            A more elegant proof might show that starting with a rectangle, you have 4 corners contributing 1/4. You can push out parts of the edges of the rectangle to generate more corners, but they will always be in pairs of opposite types.

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      3
      ·
      1 year ago
      a,b, not much to say

      The hardest part has finding the right dart ascii library to use (by default dart treats everything as UTF-16, which is horrible for this sort of thing) and the right data structure (linked hash map, which is a map that remembers insertion order.)

      • gerikson@awful.systemsOP
        link
        fedilink
        English
        arrow-up
        4
        ·
        1 year ago
        spoiler

        “you have linked hash maps? LUXURY!”

        In my code I had to resort to this sorting

        for my $lens ( sort { $Boxes->[$idx]{$a}{pos} <=> $Boxes->[$idx]{$b}{pos} }  keys %{ $Boxes->[$idx] } )
        

        Perl at its best!

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      3
      ·
      11 months ago
      1. After this problem, I will create a new reply in the OP if it is not there already, and will discuss under that thread.
      a,b

      So, like many other problems from this year, this is one of those direct solution problems where there isn’t much of a neat trick to getting the answer. You just have to implement the algorithm they specify and hope you can do it correctly.

      a) I used a regex to do some parsing because I haven’t looked at dart regex much and wanted to dip my toes a little.

      I considered doing this “properly” with OO classes and subclasses for the different rules. I felt that it would be too difficult and just wrote something janky instead. In hindsight, this was probably the wrong choice, especially since grappling with all the nullable types I had in my single rule class became a little too complex for my melting brain (it is HOT in Australia right now; also my conjunctivae are infected from my sinus infection. So my current IQ is like down 40 IQ points from its normal value of probably -12)

      b) There may have been a trick here to simplify the programming (not the processing). Again, I felt that directly implementing the specified algorithm was the only real way forward. In brief:

      1. Start with an “open” set containing a part with 4 ranges from [1, 4001) and an “accepted” set that is empty.
      2. Start at the workflow “in”
      3. For each rule in the current workflow:
      • If the rule accepts part of the ranges in the open set, remember those ranges in a closed set and remove them from the open set.
      • Remove anything the rule rejects from the open set.
      • If the rule redirects to a different workflow W, split off the applicable ranges and recurse at 3 with the current workflow as W.
      • Keep in the open set anything the rule doesn’t consider.

      Because this is AOC, I assumed that the input would be nice and wouldn’t have anything problematic like overlapping ranges, and I was right. I had a very stupid off by one error that took me a while to find as well.

      The code I have up as of this comment is pretty long and boring, I might try clean it up later.

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      3
      ·
      1 year ago
      1. I have contracted some kind of sinus infection, so rn I have a -20 IQ debuff.
      a,b

      part a: nothing to say here.

      part b: Before diving into the discussion, I timed how long 1000 cycles takes to compute, and apparently, it would take 1643175 seconds or just over 19 days to compute 1 billion cycles naively. How fun!

      So, how do you cut down that number? First, that number includes a sparse map representation, so you can tick that box off.

      Second is intuiting that the result of performing a cycle is cyclical after a certain point. You can confirm this after you’ve debugged whatever implementation you have for performing cycles- run it a few times on the sample input and you’ll find that the result has a cycle length of 7 after the second interaction.

      Once you’ve got that figured out, it’s a matter of implementing some kind of cycle detection and modular arithmetic to get the right answer without having to run 1000000000 cycles. For the record, mine took about 400 cycles to find the loop.

      • zogwarg@awful.systems
        link
        fedilink
        English
        arrow-up
        2
        ·
        edit-2
        1 year ago
        a,b

        I took a very similar approach to parts a and b, with the difference that i was too lazy to do titling in each direction, and wanted to abuse regex so Instead i always titled up and rotated, which given my method of tilting up and rotating had some satisfying cancelling of transpose operations: https://github.com/zogwarg/advent-of-code/blob/main/2023/jq/14-b.jq

        # Relevant portion
        # oneCycle expects an array, of array of chars (3x3 eg: [[".","#","."],[".",".","."],["O",".","#"]])
        def oneCycle:
          # Tilt UP          = T . MAP(scan) . T
          # Rotate           = T . MAP(reverse)
          # Titl UP . Rotate = T . MAP(scan) . Map(reverse) | T . T = Identity
          def tilt_up_rotate:
              transpose          # Gets subgroups    # Within each group,
                                 # starring with "#" # In order 1 "#", x "O", y "." 
            | map( ("#" + add) | [ scan("#[^#]*")    | ["#", scan("O"), scan("\\.")] ] | add[1:])
            | map(reverse)
          ;
          # Tilt   North,            West,           South,            East
          tilt_up_rotate | tilt_up_rotate | tilt_up_rotate | tilt_up_rotate
        ;
        

        JQ does allow some nice sortcuts sometimes, again transpose is nice to have.

        • swlabr@awful.systems
          link
          fedilink
          English
          arrow-up
          2
          ·
          1 year ago
          neat!

          I need like 25 more IQ points before I think of using a transpose in any context

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      2
      ·
      1 year ago
      a,b

      a. while you can brute force this one in time, one simple trick to make it faster is to treat the symbols as bits and interpret the grid as numbers. It then becomes a matter of locating the reflection point.

      b. It’s not much of a difference to solve b. The trick here is that if you did the bit stuff I suggested above, you’d quickly realise that a smudge interpreted as a binary number is a power of two. Finding a smudge is equivalent to if the bitwise XOR of two numbers is a power of 2, which can be done with some bitwise magic.

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      2
      ·
      11 months ago

      17, We’re in the back third now, folks!

      a, b

      A and B were roughly the same difficulty.

      So in my first year in university, in my intro DSA class, we learned A*. Have I learned any other ways to search since? Not really. So I used A* here.

      It took way longer than it should have to solve, which I blame on my ongoing illness. The main sticking point was that I implemented a class to represent the search state, and since I was going to use it as a key to a map, I implemented a hashcode for it. The rest of the A* code freely flowed from my brain (really the wikipedia pseudocode).

      Cue like 40 mins plus of wondering how the test input was searching over millions of states, wondering if I’d fucked up the A* implementation, wondering if the problem was too big for A*, and wondering if it was finally time to take a days break from all the aoc nonsense.

      Anyway at some point I realised I forgot to implement the corresponding equals method to my hashcode. Once I had that my code ran in seconds and everything was fine. This sickness is the worst!!!

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      2
      ·
      1 year ago

      12

      a,b

      Finally! a Dee Pee!!!

      This problem was mainly testing:

      • Are you a bad enough dude to formulate this DP correctly?
      • Are you a bad enough dude to choose a good DP state storage schema?
      • Is your computer a bad enough dude to store all the DP states if you choose a bad storage schema?

      …which is true of all DP problems, honestly. So given you know and understand how to approach DP problems, this would be more an engineering issue than anything.

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      2
      ·
      edit-2
      11 months ago
      16

      So, as I’ve been doing the AoC things, I’ve been creating small libraries of convenience functions to reuse, hopefully. This time, I reused some things I wrote for problem 10, which was vindicating.

      a. was a fun coding exercise. Not much more to say.

      b. I lucked out by making a recursive traversal function for a), which let me specify the entry and direction of where my traversal would start. Besides that, similar to a., this was a fun coding exercise. I was surprised that my code (which just ran the function from a) on every edge tile) It only took 2s to run; I thought I might need to memoize some of the results.

      • zogwarg@awful.systems
        link
        fedilink
        English
        arrow-up
        2
        ·
        11 months ago
        16 a,b

        Neat!

        In my case it was a lot more of headbanging, the traverse function i wrote for part a was way to slow, since JQ isn’t happy with loop-heavy assignments (if not buried within C-implemented builtins). Part a completed in ~2seconds, which was never going to do (in hindsight it would have taken me less time to simply let it run slowly), I had to optimize it so that the beams don’t step one square at a time, but shoot straight to any obstacle.

        It took me waaaay too long to troubleshoot it into something that actually worked. I’m sure there’s a compact implementation out there, but my part b ended up looking very meaty (and still took ~30s to run): https://github.com/zogwarg/advent-of-code/blob/main/2023/jq/16-b.jq

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      1
      ·
      1 year ago

      11

      a,b

      a: So, I’ve been in the habit of skipping the flavour text and glossing over the prompt. This time, it hurt me badly.

      I read the problem as follows: for N galaxies, find N/2 pairings such that the sum of distances is minimal.

      At this point, I was like, wow, the difficulty has ramped up. A DP? That will probably run out of memory with most approaches, requiring a BitSet. I dove in, eager to implement a janky data structure to solve this humdinger.

      I wrote the whole damn thing. The bitset, the DP, everything. I ran the code, and WOAH, that number was much smaller than the sample answer. I reread the prompt and realised I had it all wrong.

      It wasn’t all for naught, though. A lot of the convenience stuff I’d written was fine. Also, I implemented a sparse parser, which helped for b.

      b: I was hoping they were asking for what I had accidentally implemented for a. My hopes were squandered.

      Anyway, this was pretty trivial with a sparse representation of the galaxies.

  • gerikson@awful.systemsOP
    link
    fedilink
    English
    arrow-up
    1
    ·
    1 year ago

    Day 11: Cosmic Expansion

    https://adventofcode.com/2023/day/11

    discussion

    After yesterday’ fiddle-fest we are back with a straight-forward puzzle. Today we get the return of Manhattan distance, an AoC fav, but this time not spelled out to fool the crafty LLMs.

    I made the initial decision not to “move” the galaxies in the initial map, but instead to store an offset that was increased whenever an empty row or column preceding the object was detected. This turned out to make part 2 really easy once I figured out the off-by-one error.

    • zogwarg@awful.systems
      link
      fedilink
      English
      arrow-up
      1
      ·
      1 year ago
      discussion

      In retrospect that would have been far better for runtime, my dist function ended up being a tad expensive.

      I substituted the rows/columns, with multiplication by the expansion rate if they were all numbers. And then for each galaxy pair do a running sum by going “down” the “right” and adding the distance for each row and column crossed.

      https://github.com/zogwarg/advent-of-code/blob/main/2023/jq/11-b.jq

      transpose is nice to have in that approach.

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      1
      ·
      1 year ago
      1. Not my best work. No code in this comment, just check 5.dart if you want to see it in the github repo linked above.
      General

      I used a class this time because it looked like it might be helpful. I don’t think it turned out to be that useful. Still, you can see Dart’s interesting constructor and getter syntax on display.

      a.

      Pretty straightforward, though I didn’t read the format correctly and had the destination/source data reversed. Oops! Luckily, my performance here will in no way affect my future career.

      b.

      I didn’t read the prompt correctly, which tripped me up. Also, while my solution is correct, it assumes that the input could be trickier than what the problem threw at me. Specifically, the edge case I had in mind was a range of values split into many subintervals and needing to track those mappings. I threw in some print statements to discover that intervals were never split beyond two subintervals, which was disappointing. Oh well- being correct is the best feeling you can have if you are otherwise empty inside.

      Other than processing the input in the form of intervals, I don’t think there were any notable tricks at play here, so this was more of an exercise in implementing code cleanly, which I struggle with.

    • zogwarg@awful.systems
      link
      fedilink
      English
      arrow-up
      0
      ·
      1 year ago

      Cleaned up version of code used to solve part 2 in jq.

      Spoiler code section
      #!/usr/bin/env jq -n -R -f
      
      # Get LR instructions
      ( input / "" | map(if . == "L" then 0 else 1 end )) as $LR |
      ( $LR | length ) as $l |
      
      # Make map {"AAA":["BBB","CCC"], ...}
      (
        [
          inputs | select(.!= "") | [ scan("[A-Z]{3}") ] | {(.[0]): .[1:]}
        ] | add
      ) as $map |
      
      # List primes for GCM test / factorization
      [
        2, 3, 5, 7, 11, 13, 17, 19,
        23, 29, 31, 37, 41, 43, 47,
        53, 59, 61, 67, 71, 73, 79,
        83, 89, 97
      ] as $primes |
      
      reduce (
        $map | keys[] | select(test("..A")) | { s: 0, i: 0, c: .} |
      
        # For each "..A" starting position
        # Produce visited [ "KEY", pos mod $l ], until loop is detected
        until (.i as $i | .[.c] // [] | contains([$i]);
          .s as $s | .i as $i | .c as $c        |
          $map[$c][$LR[$i]] as $next            | # Get next KEY
          .[$c] = (( .[$c] // [ $s ] ) + [$i] ) | # Append ( .s ≡ $l ) to array for KEY (first = .s non mod)
          .s = ( $s + 1 )  | .i = (.s % $l )    | # Update cursors, for next iteration
          .c = $next
        )
        | .[.c][0] as $start_loop_idx | (.s - $start_loop_idx) as $loop_size
        | [ to_entries[] | select(.key[-1:] == "Z") ]
        | if (
            length != 1                                           # Only one ..Z per loop
            or ( .[0].value[0] != $loop_size )                    # First ..Z idx = loop size
            or ( [ .[0].value[0] / $l ] | inside($primes) | not ) # loop_size = ( prime x $l )
            or ( .[0].value[0] / $l  > $l )                       # GCM(loop_sizes) = $l
          ) then "Input does not fit expected pattern" | halt_error else
            # Under these conditions, synched positions of ..Zs happen at:
            # LCM = Π(loop_size_i / GCM) * GCM
      
            # loop_size_i / GCM
            .[0].value[0] / $l
          end
      ) as $i (1; . * $i)
      
      # Output LCM = first step where, all ghosts are on "..Z" nodes
      | . * $l
      
      • swlabr@awful.systems
        link
        fedilink
        English
        arrow-up
        0
        ·
        1 year ago

        Seeing you implement gcd/lcm makes me think about the people who are gunning for the AoC leaderboards. What do they have that I don’t?

        Asking out of general interest and not any sort of feelings of inadequacy (I swear, behind a face of gritted teeth and obvious seethe).

        Like, do they have cron scripts to scrape the prompt and input as soon as it comes out? Libraries of util functions accrued from years of AoC participation? That’s all I’ve thought of and honestly it doesn’t sound implausible if you are hypercompetitive. Like I imagine they just have a raiders of the lost ark warehouse of boilerplate indexed in their memory palace to draw from. And I don’t have that and I am totally not envious at all.

        • gerikson@awful.systemsOP
          link
          fedilink
          English
          arrow-up
          1
          ·
          1 year ago

          Re: LCM, I figured my favorite Perl library ntheory had it, and I was right! This a godsend for Project Euler, too.

          (The first year of AoC leaned heavily on these kinds of problems, and Python itertools utterly destroyed the puzzles.)

          Re: leaderboard participants - I believe many of them are involved in programming contests, generally, and if you do enough of these, you recognize patterns, and you have routines for a lot of stuff. Also there are tools to download the puzzle inputs automatically.

          My personal take on how to do AoC: https://gerikson.com/blog/comp/adventofcode/Howto-AoC.html (maybe already posted, I don’t care)