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.

  • 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.