Skip to content

Instantly share code, notes, and snippets.

@skanev
Last active February 21, 2024 13:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save skanev/c38ad683d74f07666befa56390fc1977 to your computer and use it in GitHub Desktop.
Save skanev/c38ad683d74f07666befa56390fc1977 to your computer and use it in GitHub Desktop.
Advent of Code 2022 in Ruby (sort of)

Advent of Code 2022 solutions in Perl Ruby

Just for kicks, I'm trying to solve them with semi-golfed Ruby. That is:

  • As short as possible, but
  • Have some whitespace to make them somewhat readable
  • Avoid single-letter identifiers to keep them somewhat readable
  • Don't make them too short when they are short enough

Each solution parses the input file and outputs the answer.

p File.read('input').split(/\n\n/).map { _1.scan(/\d+/).map(&:to_i).sum }.max
p File.read('input').split(/\n\n/).map { _1.scan(/\d+/).map(&:to_i).sum }.max(3).sum
p File.read('input').tr(' ', '').split.sum { %w(_ BX CY AZ AX BY CZ CX AY BZ).index _1 }
p File.read('input').tr(' ', '').split.sum { %w(_ BX CX AX AY BY CY CZ AZ BZ).index _1 }
p File.read('input').lines.sum { ' Mn6DFChul'.index _1.crypt('aa')[3] }
p File.read('input').lines.sum { ' MhDuFnC6l'.index _1.crypt('aa')[3] }
def score(g) = g.map { _1.map(&:chars).reduce(:&)[0] }.sum { [0, *?a..?z, *?A..?Z].index _1 }
p score File.read('input').lines.map { |s| [s[...s.size/2], s[s.size/2..]] }
p score File.read('input').lines.each_slice(3)
ranges = File.read('input').lines.map { _1.scan(/\d+/).map(&:to_i) }.map { |(a, b, c, d)| [a..b, c..d] }
p ranges.count { |a, b| a.cover?(b) || b.cover?(a) }
p ranges.count { |a, b| a.begin <= b.end && b.begin <= a.end }
def solve(fn)
first, second = File.read('input').split("\n\n")
stacks = first.split("\n").map(&:chars).transpose.each_slice(4).map { _1[1].grep(/[A-Z]/).reverse }
second.lines.each do |line|
num, from, to = line.scan(/\d+/).map(&:to_i)
stacks[to-1] += fn.(num.times.map { stacks[from-1].pop }.compact)
end
stacks.map(&:last).join
end
puts solve -> { _1 }
puts solve -> { _1.reverse }
p File.read('input').chars.each_cons(4).with_index.select { |w, i| w.uniq == w }.map { _1.size + _2 }.first
p File.read('input').chars.each_cons(14).with_index.select { |w, i| w.uniq == w }.map { _1.size + _2 }.first
require 'pathname'
cwd = Pathname('/')
sizes = {cwd => 0}
File.read('input').lines.each do |line|
case line
when /^\$ cd (.*)/ then cwd /= $1
when /^dir (.*)/ then sizes[cwd / "#$1/"] = 0
when /^(\d+) (.*)$/ then sizes[cwd / $2] = $1.to_i
end
end
dirs = sizes.keys.map(&:to_s).grep(/\/$/).to_h { |d| [d, sizes.filter { _1.to_s.start_with? d }.sum { _2 }] }
delta = dirs['/'] - 40000000
p dirs.values.filter { _1 <= 100_000 }.sum
p dirs.values.filter { _1 >= delta }.min
require 'matrix'
visible, score, grid = 0, 0, Matrix[*File.read('input').lines.map { _1.chomp.chars.map(&:to_i) }]
grid.each_with_index do |h, x, y|
lines = [grid.row(x)[...y], grid.row(x)[y+1...].reverse, grid.column(y)[...x], grid.column(y)[x+1...].reverse]
visible += 1 if lines.any? { |line| line.all? { _1 < h } }
score = [score, lines.map { |line| line.reverse.chunk_while { _1 < h }.first&.count || 0 }.reduce(:*)].max
end
p visible, score
one, two, rope = {}, {}, 10.times.map { 0i }
File.read('input').lines.each do |line|
line[2..].to_i.times do
rope[0] += 1i ** 'LURD'.index(line[0])
1.upto(9).lazy.map { [rope[_1 - 1] - rope[_1], _1] }.each do |d, i|
rope[i] += Complex(*d.rect.map { _1 <=> 0 }) unless d.abs2 <= 2
end
one[rope[1]], two[rope[9]] = true, true
end
end
p one.keys.size, two.keys.size
cycle, reg, sum, screen = 0, 1, 0, 6.times.map { "" }
File.read('input').lines.each do |line|
(line =~ /addx/ ? 2 : 1).times do
screen[cycle / 40][cycle % 40] = (reg - cycle % 40).abs <= 1 ? ?# : ?.
cycle += 1
sum += reg * cycle if cycle % 40 == 20
end
reg += line[5..].to_i
end
puts sum, screen
input = File.read('input').split("\n\n")
.map { [_1[/.*O/m].scan(/\d+/).map(&:to_i)[1..], _1[/= (.*)/, 1], *_1[/T.*/m].scan(/\d+/).map(&:to_i), 0] }
mod = input.map { _1[2] }.reduce(:*)
{20 => -> { _1 / 3 }, 10000 => -> { _1 % mod }}.each do |turns, fn|
monkeys = input.map { _1.map(&:dup) }
turns.times do
monkeys.each do |monkey|
monkey[0].each do |old|
new = fn[eval(monkey[1])]
monkeys[monkey[3 + (new % monkey[2] <=> 0)]][0] << new
monkey[5] += 1
end.clear
end
end
p monkeys.map(&:last).max(2).reduce(:*)
end
m,c,_=[],[0]*9,File.read('input');{/M.*(\d+):\n.*:(.*)/=>'i=\1;BEGIN{m[\1]||=[\2]}',/O.*:/=>"while old=m[i].pop;",/T.* (.*)\n/=>'new/=3;m[0==new% \1?',/ I.*?(\d+).*?(\d+)/m=>'\1:\2]<<new;c[i]+=1end'}.each{_.gsub! *_1};eval _*20;p eval c.max(2)*?*;m,c=[],[0]*9;_.gsub!'/=3',"%=#{eval _.scan(/% (\d+)/)*?*}";eval _*10000;p eval c.max(2)*?*
map = File.read('input').lines.flat_map.with_index { |l, x| l.chars.map.with_index { [x + _2.i, _1] } }.to_h
start, target = map.invert['S'], map.invert['E']
map[start], map[target] = 'a', 'z'
seen, left = {}, [[target, 0]]
until left.empty?
p, steps = left.shift
next if seen[p] && seen[p] <= steps
seen[p] = steps
left += [p + 1, p - 1, p + 1i, p - 1i].select { map[_1] && map[_1].ord + 1 >= map[p].ord }.map { [_1, steps + 1] }
end
p seen[start]
p map.filter { |k, v| v == 'a' }.map { |k, v| seen[k] }.compact.min
def cmp(a, b)
case [a, b]
in Integer, Integer then a <=> b
in Array, Array then a.take(b.size).zip(b).lazy.map { cmp _1, _2 }.detect { _1 != 0 } || a.size <=> b.size
else cmp Array(a), Array(b)
end
end
pairs = File.read('input').split("\n\n").map { |c| c.lines.map { eval _1 } }
p pairs.map.with_index.sum { cmp(*_1) <= 0 ? _2 + 1 : 0 }
p pairs.reduce(:+).concat([[[2]], [[6]]]).sort { cmp _1, _2 }.then { _1.index([[2]]).succ * _1.index([[6]]).succ }
map = File.read('input').lines.flat_map { |l| l.split(' -> ').map { _1.scan(/-?\d+/).map(&:to_i) }.each_cons(2).to_a }
.flat_map { |pair| pair.transpose.map(&:minmax).map { [*_1.._2] }.reduce(:product).map { _1 + _2.i } }.to_set
def flow(map, point, max, &)
return if map.include?(point) || point.imag == max + 2
[0, -1, 1].each { |d| flow(map, point + d + 1i, max, &) }
map.add point
yield point
end
origin, max = 500, map.map(&:imag).max
p enum_for(:flow, map.dup, origin, max).find_index { _1.imag > max }
p enum_for(:flow, map.dup, origin, max).count
SPAN = 0..4000000
input = File.read('input').lines.map { _1.scan(/-?\d+/).map(&:to_i).each_slice(2).map { |x, y| x + y.i } }
def dist(a, b) = (a - b).rect.map(&:abs).sum
def project((s, b), y) = (dist(b, s) - dist(s, s.real + y.i)).then { s.real - _1 .. s.real + _1 }
def overlaps?(a, b) = a.begin <= b.end && b.begin <= a.end
def merge(a, b) = [a.begin, b.begin].min .. [a.end, b.end].max
def ranges(input, y)
ranges = input.map { project(_1, y) }.filter { _1.size > 0 && overlaps?(SPAN, _1) }.sort_by(&:begin)
old, ranges = ranges, ranges.chunk_while(&method(:overlaps?)).map { _1.reduce(&method(:merge)) } while ranges != old
ranges
end
rs = ranges(input, SPAN.end / 2)
p rs.sum(&:size) - input.filter { |b, s| s.imag == SPAN.end / 2 && rs.any? { _1 === s.real } }.uniq { _2.real }.size
p SPAN.lazy
.flat_map { |y| ranges(input, y).each_cons(2).filter { _1.end+2 == _2.begin && SPAN === _1.end+1 }.map { [_2, y] } }
.map { _1.begin.pred * 4000000 + _2 }.first
$valves = File.read('input').lines.to_h { [_1[6..7], [_1[/\d+/].to_i, _1[/to \w+ (.*)/, 1].split(', ')]] }
def flow(open) = open.uniq.map { |k| $valves[k][0] }.sum
def go(t, a, b, s, seen, open, move_one, &)
yield s if t == 1
return if t == 1 || seen.fetch([a, b, t], -1) >= s
seen[[a, b, t]] = s
possible = (!open.index(a) && $valves[a][0] > 0 ? [[a, open + [a]]] : []) + $valves[a][1].map { [_1, open] }
possible = [[a, open]] unless move_one
possible.each do |a, open|
go(t - 1, a, b, s + flow(open + [b]), seen, open + [b], move_one, &) if !open.index(b) && $valves[b][0] > 0
$valves[b][1].each { |b| go(t - 1, a, b, s + flow(open), seen, open, move_one, &) }
end
end
p enum_for(:go, 30, 'AA', 'AA', 0, {}, [], false).max
p enum_for(:go, 26, 'AA', 'AA', 0, {}, [], true).max
SHAPES = [[0, 1, 2, 3], [1, 1i, 1+1i, 2+1i, 1+2i], [0, 1, 2, 2+1i, 2+2i], [0, 1i, 2i, 3i], [0, 1, 1i, 1+1i]]
def move(dots, cave, dir) = dots.map { _1 + dir }.then { fits?(cave, _1) ? _1 : dots }
def fits?(cave, dots) = dots.all? { 0 <= _1.imag && (0..6).include?(_1.real) && !cave.include?(_1) }
tick, rock, cave, max, seen, done, wind = 0, nil, Set[], -1, {}, nil, File.read('input').chomp.chars.map { _1.ord - 61 }
wind.each.with_index.cycle.each do |delta, beat|
rock, tick = SHAPES[tick % SHAPES.size].map { _1 + max.i + 5i + 2 }, tick + 1 if rock.nil?
rock = rock.then { move _1, cave, -1i }.then { move _1, cave, delta }
next if move(rock, cave, -1i) != rock
cave, max = cave + rock, [max, *rock.map(&:imag)].max
rock, finger = nil, [tick % SHAPES.size, beat, [*0..30].product([*0..6]).map { cave.member? max.i - _1.i + _2 }]
if done
done[0] -= 1
puts "Part 2: #{max + 1 + done[1]}" if done[0] == 0
exit if done[0] <= 0 && tick > 2022
elsif match = seen[finger]
height, left = (10**12 - tick).divmod(tick - match[0])
done = [left, height * (max + 1 - match[1])]
end
seen[finger] = [tick, max + 1]
puts "Part 1: #{max + 1}" if tick == 2022
end
def sides((x, y, z)) = [[x + 1, y, z], [x - 1, y, z], [x, y + 1, z], [x, y - 1, z], [x, y, z + 1], [x, y, z - 1]]
cubes = File.read('input').lines.map { _1.scan(/\d+/).map(&:to_i) }.to_set
span = cubes.flat_map { _1 }.minmax.then { _1 - 1 .. _2 + 1 }
left, seen, sum = [[span.begin] * 3], {}, 0
until left.empty?
point = left.pop
next if seen[point]
seen[point] = point
sum += sides(point).count { cubes.include?(_1) }
left += sides(point).reject { _1.grep(span).count < 3 || seen[_1] || cubes.member?(_1) }
end
p (cubes.flat_map { sides(_1) } - cubes.to_a).size, sum
def solve((_, a, b, c, d, e, f), t, seen = Set[])
->(go, yielder, s) do
yielder << s[-1] if s[0] == 0
s[1..3] = s[1..3].zip([[a, b, c, e].max, d, f]).map(&:min)
s[5..7] = s[5..7].zip(s[1..3], [[a, b, c, e].max, d, f]).map { |c, r, m| [c, s[0] * m - r * (s[0] - 1)].min }
return unless seen.add?(s) and s[0] > 0
base = s.zip([-1, 0, 0, 0, 0, *s[1..4]]).map { _1 + _2 }
go.call go, yielder, base.dup
go.call go, yielder, base.dup.tap { _1[1] += 1; _1[5] -= a } if s[5] >= a
go.call go, yielder, base.dup.tap { _1[2] += 1; _1[5] -= b } if s[5] >= b
go.call go, yielder, base.dup.tap { _1[3] += 1; _1[5] -= c; _1[6] -= d } if s[5] >= c and s[6] >= d
go.call go, yielder, base.dup.tap { _1[4] += 1; _1[5] -= e; _1[7] -= f } if s[5] >= e and s[7] >= f
end.then { |go| Enumerator.new { go.(go, _1, [t, 1, 0, 0, 0, 0, 0, 0, 0]) }.max }
end
p File.read('input').lines.map { _1.scan(/\d+/).map(&:to_i) }.sum { _1[0] * solve(_1, 24) }
p File.read('input').lines.map { _1.scan(/\d+/).map(&:to_i) }.take(3).map { solve _1, 32 }.reduce(:*)
Node = Struct.new :val, :before, :after
def numbers() = File.read('input').lines.map(&:to_i)
def answer(ns) = Enumerator.produce(ns.find { _1.val == 0 }.after, &:after).each_slice(1000).take(3).sum { _1.last.val }
def parse() = numbers.map { Node.new _1.to_i }.tap { (_1 + [_1[0]]).each_cons(2) { |a, b| a.after, b.before = b, a } }
def rotate(items)
items.each do |item|
item.before.after, item.after.before = item.after, item.before
(item.val % items.count.pred).times { item.before, item.after = item.after, item.after.after }
item.after.before = item.before.after = item
end
end
p answer(rotate(parse)), answer(10.times.reduce(parse.each { _1.val *= 811589153 }) { rotate _1 })
eval File.read('input').gsub(/^(\w+): /, 'def \1() =')
p root
class Proc
def +(other) = -> { _1 ? call(_1 - other.real) : call(other) }
def *(other) = -> { call _1 / other.real }
def -(other) = -> { call other.real? ? other.real + _1 : other.real - _1 }
def /(other) = -> { call other.real? ? other.real * _1 : other.real / _1 }
def coerce(other) = [self, other + 1i]
end
def humn() = ->(x) { x }
p root.(nil)
SIZE, IDS, ADJ, EDGES = 50, [1, 2, 4, 6, 7, 9], {1+0i => 0, 1i => 1, -1+0i => 1+1i, -1i => 1i}, {
2+1i => [9, 1], 9-1i => [2, 1], 1 => [7, -1], 6 => [2, -1], 2 => [6, -1], 7 => [1, -1], 1+1i => [9, 1i],
10 => [1, -1i], 6+1i => [4, 1i], 5 => [6, -1i], 7-1i => [9, 1i], 8 => [7, -1i], 2-1i => [4, 1i], 3 => [2, -1i]
}.transform_keys(&:to_c)
def id(pos) = (pos.imag / SIZE) * 3 + pos.real / SIZE
def origin(face) = ((face / 3) * SIZE).i + ((face % 3) * SIZE)
def clamp(point) = point.real % SIZE + (point.imag % SIZE).i
def step(p, d) = IDS.include?(id(p + d)) && [p + d, d]
def wrap(grid, p, d) = step(p, d) || [Enumerator.produce(p) { _1 - d }.take_while { grid[_1] }.last, d]
def cube(_, p, d) = step(p, d) || EDGES[id(p) - d].then { [origin(_1) + clamp(p * _2 + d * _2 - ADJ[_2.to_c]), _2 * d] }
def solve(grid, path)
dir, pos = 1.to_c, grid.keys.min_by { [_1.imag, _1.real] }
path.scan(/\d+|[LR]/) do |match|
dir *= {?L => -1i, ?R => 1i}.fetch(match, 1)
match.to_i.times.lazy.take_while { grid[yield(pos, dir)[0]] != '#' }.each { pos, dir = yield pos, dir }
end
1000 * pos.imag.succ + 4 * pos.real.succ + [1, 1i, -1, -1i].map(&:to_c).index(dir)
end
a, b = File.read('input22.txt').split(/\n\n/)
grid = a.lines.flat_map.with_index { |l, x| l.chomp.chars.map.with_index { [x.i + _2, _1] }.reject { _2 == ' ' } }.to_h
p solve(grid, b) { wrap(grid, _1, _2) }, solve(grid, b) { cube(grid, _1, _2) }
SQUARE = [-1, 0, 1].product([-1, 0, 1]).map { _1 + _2.i }.reject(&:zero?)
def free?(grid, point, dirs) = dirs.none? { grid.member? point + _1 }
def proposal(grid, rules, pos) = rules.detect { |x| free? grid, pos, SQUARE.map { _1 + 2 * x }.filter { _1.abs2 <= 2 } }
def round(grid, rules) = grid.group_by { _1 + proposal(grid, rules, _1).to_c }.flat_map { _2.size == 1 ? [_1] : _2 }
def bounds(elves) = Range.new(*elves.map(&:imag).minmax).to_a.product(Range.new(*elves.map(&:real).minmax).to_a)
def score(elves) = bounds(elves).count { !elves.member?(_1.i + _2) }
lines = File.read('input').lines
elves = lines.flat_map.with_index { |l, x| l.chars.map.with_index { _1 == ?# ? x.i + _2 : nil } }.compact.to_set
seq = Enumerator.produce([elves, [0, -1i, 1i, -1, 1]]) { [round(_1, _2).to_set, _2[0..0] + _2[2..] + _2[1..1]] }
p seq.take(11).last.then { score(_1.first) }
p seq.with_index.each_cons(2).detect { |a, b| a[0][0] == b[0][0] }[1][1]
def clamp(pos) = (pos.imag % H).i + pos.real % W
input = File.read('input').lines.flat_map.with_index { |l, x| l.chomp.chars.map.with_index { |c, y| [x, y, c] } }
blizzards = input.map { |x, y, c| c != '.' && c != '#' ? [x.pred.i + y - 1, 1i ** '>v<^'.index(c)] : nil }.compact
H, W = input.map { _1[0] }.max - 1, input.map { _1[1] }.max - 1
mod, free = H.lcm(W), ([[-1, 0], [H, W - 1]] + [*0...H].product([*0...W])).map { |x, y| x.i + y }.to_set
time = Enumerator.produce(blizzards) { |b| b.map { [clamp(_1 + _2), _2] } }.take(mod).map { free - _1.map(&:first) }
start, goal = -1i, H.i + W - 1
left, seen, first, second = [[start, 0, 0]], Set[], 2 ** 62 - 1, 2 ** 62 - 1
until left.empty?
p, tick, phase = left.shift
next unless seen.add?([p, tick, phase]) && tick < second
first = [first, tick].min if phase == 0 && p == goal
second = [second, tick].min if phase == 2 && p == goal
phase = {[0, goal] => 1, [1, start] => 2}[[phase, p]] || phase
left += (time[tick.succ % mod] & [p, p + 1, p - 1, p + 1i, p - 1i]).map { [_1, tick + 1, phase] }
end
p first, second
def snafu(s) = s.chars.reverse.map.with_index { ("=-012".index(_1) - 2) * 5 ** _2 }.sum
def invert(n) = n == 0 ? "" : invert(n.succ.succ / 5) + "012=-"[n % 5]
p File.read('input').lines.map(&:chomp).map { snafu(_1) }.sum.then { invert _1 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment