zogwarg

joined 2 years ago
[–] [email protected] 4 points 2 years ago

How about not fiddling with indices?

JQ Notfiddlingwithindexificationhttps://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 ^^.

[–] [email protected] 2 points 2 years ago* (last edited 2 years ago) (1 children)

a,bI 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.

[–] [email protected] 3 points 2 years 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.

[–] [email protected] 2 points 2 years ago* (last edited 2 years ago) (2 children)

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.

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

[–] [email protected] 2 points 2 years ago

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

[–] [email protected] 3 points 2 years ago (1 children)

Ah! Thanks for making my notice the GCM -> GCD typo. I'm not gunning for the leaderboards myself, it's pretty hopeless ^^. Yes i am assuming based off of experience and utility tools.

I myself have tools to automatically get the inputs, and submit outputs, but that's more because it pleases me than to actually be fast: https://github.com/zogwarg/advent-of-code/blob/main/functions.sh

(Also completely pointlessly have a functions to extract the session cookie from chrome storage from the CLI, despite being long-lived, and therefore much simpler to simply copy-paste from debugger window)

[–] [email protected] 3 points 2 years ago (2 children)

spoilerPart 2 only, but Part 1 is very similar.

#!/usr/bin/env jq -n -R -f
[
  # For each line, get numbers eg: [ [1,2,3] ]
  inputs / " " | map(tonumber) | [ . ] |

  # Until latest row is all zeroes
  until (.[-1] | [ .[] == 0] | all;
   . += [
     # Add next row, where for element(i) = prev(i+1) - prev(i)
     [ .[-1][1:] , .[-1][0:-1] ] | transpose | map(.[0] - .[1])
    ]
  )
  # Get extrapolated previous element for first row
  |  [ .[][0] ] | reverse | reduce .[] as $i (0; $i - . )
]

# Output sum of extapolations for all lines
| add

I'm pretty sure you could make this one line and unreadable ^^.

[–] [email protected] 2 points 2 years ago (6 children)

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

[–] [email protected] 3 points 2 years ago* (last edited 2 years ago) (7 children)

Day 8: Haunted Wasteland

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

Not so easy at least for part two.

spoilerDo you remember high school math, like lowest common multiple, part 2 electric boogaloo.

[–] [email protected] 3 points 2 years ago* (last edited 2 years ago) (1 children)

Day 6: Wait For It

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

Alternate spoiler name - for part 2~~Do you remember highschool algebra?~~ Can you (or your compiler) remember highschool algebra fast enough to beat out a naïve implementation?

[–] [email protected] 3 points 2 years ago (1 children)

The main catch is it would often be faster to use a "real" programming langage ^^, both in writing the code, and in execution time for some loop heavy examples: equivalent code that completes say in 1 second in python, completing in 1 minute in jq. Also missing a way to call native libraries, to do stuff like say "md5" (relevant) in past years advents-of-code.

That being said i like the general "pipe", map-reduce feel of the language. Like bash one-liners It can make for very terse implementations. I like to add comments, and indentation to make it readable though.

[–] [email protected] 4 points 2 years ago* (last edited 2 years ago) (4 children)

I liked the slight trickiness of part 2, that the naive implementation would never complete in time.

As always doing a JQ implementation:

Part 1

#!/usr/bin/env jq -n -R -f

# Get seeds
input | [ match("\\d+"; "g").string | tonumber ] as $seeds |

# Collect maps
reduce inputs as $line ({};
  if $line == "" then
    .
  elif $line | test(":") then
    .k = ( $line / " " | .[0] )
  else
    .[.k] += [[ $line | match("\\d+"; "g").string | tonumber ]]
  end
)

# For each map, apply transformation to all seeds.
# seed -> ... -> location
| reduce ( to_entries[] | select(.key != "k") .value) as $map ({s:$seeds};
  .s[] |= (
    # Only attempt transform if element is in one of the ranges
    [ . as $e | $map[] | select(. as  [$d,$s,$l] | $e >= $s and $e < $s + $l) ] as $range |
    if ($range | length ) > 0 then
      $range[0] as [$d,$s] |
      . - $s + $d
    else
      .
    end
  )
)

# Get lowest location
| .s | min

Some comments:

  • A nice use of input first to get the seeds, then inputs to get remaining lines.

Part 2

#!/usr/bin/env jq -n -R -f

# Utility function
def group_of($n):
  ( length / $n ) as $l |
  . as $arr |
  range($l) | $arr[.*$n:.*$n+$n]
;

# Get all seed ranges
input | [ match("\\d+"; "g").string | tonumber ] | [group_of(2)] as $seeds |

# Collect maps
reduce inputs as $line ({};
  if $line == "" then
    .
  elif $line | test(":") then
    .k = ( $line / " " | .[0] )
  else
    .[.k] += [[ $line | match("\\d+"; "g").string | tonumber ]]
  end
)

# For each map, apply transformation to all seeds ranges.
# Producing new seed ranges if applicable
# seed -> ... -> location
| reduce (to_entries[] | select(.key != "k") .value) as $map ({s:$seeds};
  .s |= [
    # Only attempt transform if seed range and map range instersect
    .[] | [.[0], add, .[1] ] as [$ea, $eb, $el] | [
      $map[] | select(.[1:] | [.[0], add ] as [$sa,$sb] |
        ( $ea >= $sa and $ea < $sb ) or
        ( $eb >= $sa and $eb < $sb ) or
        ( $sa >= $ea and $sa < $eb )
      )
    ] as $range |
    if $range | length > 0 then
      $range[0] as [$d,$s,$l] |
      # ( only end ) inside map range
      if $ea < $s and $eb < $s + $l then
        [$ea, $s - $ea], [$d, $eb - $s ]
      # ( both start, end ) outside map range
      elif $ea < $s then
        [$ea, $s - $ea], [$d, $l], [ $s + $l, $eb ]
      # ( only start ) inside map range
      elif $eb > $s + $l then
        [$ea + $d - $s, $l - $ea + $s ], [$s + $l, $eb - $s - $l]
      # ( both start, end ) inside map range
      else
        [$ea + $d - $s , $el]
      end
    else
      .
    end
  ]
)

# Get lowest location
| [.s[][0]] | min

Some comments:

  • Since iterating across all seeds naively would take forever, iterating over seed ranges instead.
  • It's nice that JQ can neatly produce extra elements: [1,2,3] | [ .[] | if . == 2 then . * 10 + 1 , . * 10 + 2 else . end ] -> [1, 21, 22, 3]
  • There is probably a more compact way of expressing all the conditions and produced outputs.

Replaced less-than (and greater-than for symmetry) symbols with full-width version, since lemmy apparently doesn't handle them well within a code block: replacing less than with <

view more: ‹ prev next ›