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