Skip to content

Instantly share code, notes, and snippets.

@cjavdev
Last active December 18, 2023 16:04
Show Gist options
  • Save cjavdev/d15a2a4ffed6c840c2fb28a093e9f927 to your computer and use it in GitHub Desktop.
Save cjavdev/d15a2a4ffed6c840c2fb28a093e9f927 to your computer and use it in GitHub Desktop.
Advent of Code 2023
# Part 1
# input = <<~INPUT
# 1abc2
# pqr3stu8vwx
# a1b2c3d4e5f
# treb7uchet
# INPUT
#
# result = DATA.readlines.map do |line|
# digits = line.scan(/\d/)
# (digits.first + digits.last).to_i
# end.sum
# p result
# Part 2
input = <<~INPUT
two1nine
eightwothree
abcone2threexyz
xtwone3four
4nineeightseven2
zoneight234
7pqrstsixteen
INPUT
WORD_TO_DIGIT = {
"one" => "1",
"two" => "2",
"three" => "3",
"four" => "4",
"five" => "5",
"six" => "6",
"seven" => "7",
"eight" => "8",
"nine" => "9",
"1" => "1",
"2" => "2",
"3" => "3",
"4" => "4",
"5" => "5",
"6" => "6",
"7" => "7",
"8" => "8",
"9" => "9",
"0" => "0",
}
words = WORD_TO_DIGIT.keys
r_words = words.map(&:reverse)
result = DATA.readlines.map do |line|
digits = line.match(/(#{words.join("|")})/, 0)
first = WORD_TO_DIGIT[digits[0]]
digits = line.reverse.match(/(#{r_words.join("|")})/, 0)
last = WORD_TO_DIGIT[digits[0].reverse]
(first + last).to_i
end.sum
p result
input = <<~INPUT
Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
INPUT
class Game
attr_reader :id, :rounds
def self.parse(line)
first, *last = line.chomp.split(/[:;]/)
id = first.split.last.to_i
rounds = last.map do |str_round|
str_round.split(',').map do |str_card|
count, color = str_card.split
[color.to_sym, count.to_i]
end.to_h
end
new(id, rounds)
end
def initialize(id, rounds)
@id = id
@rounds = rounds
end
def max_cubes
rounds
.each_with_object({
red: 0,
blue: 0,
green: 0
}) do |round, counts|
round.each do |color, count|
counts[color] = count if count > counts[color]
end
end
end
def max_power
max_cubes.values.inject(:*)
end
def possible?
max_cubes in {
red: ..12,
green: ..13,
blue: ..14,
}
end
end
data = input.each_line
r = data.map do |line|
g = Game.parse(line)
g.max_power
# p line
# p g.max_cubes
# p g.max_power
# if g.possible?
# g.id
# else
# 0
# end
end
p r.sum
__END__
require "set"
input = <<~INPUT
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
INPUT
puts input
rows = input.each_line.map(&:chomp)
# rows = DATA.readlines.map(&:chomp)
syms = []
rows.each_with_index do |row, x|
row.each_char.with_index do |char, y|
# next if char == "."
# next if char =~ /\d/
# p [char, x, y]
syms << [x, y] if char == "*"
end
end
sum = 0
syms.each do |(x, y)|
number_starts = Set.new
[
[-1, -1], [-1, 0], [-1, 1],
[0, -1], [0, 1],
[1, -1], [1, 0], [1, 1],
].each do |(dx, dy)|
nx = x + dx
ny = y + dy
if rows[nx][ny] =~ /\d/
nny = ny
while rows[nx][nny - 1] =~ /\d/
nny -= 1
end
number_starts << [nx, nny]
end
end
if number_starts.length == 2
result = number_starts.map do |(x, y)|
rows[x][y..].to_i
end.inject(:*)
sum += result
end
end
p sum
<<~INPUT => input
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
INPUT
# input.each_line => data
DATA.readlines => data
card_counts = Hash.new {|h, k| h[k] = 1}
data.map do |card|
card.split => _, id, *numbers
id.to_i => id
numbers => *winning, "|", *ours
(winning & ours).size => match_count
card_counts[id].times do
match_count.times do |i|
card_counts[id + i + 1] += 1
end
end
match_count > 0 ? 2 ** (match_count - 1) : 0
end => scores
p card_counts.values.sum
p scores.sum
require 'set'
input = <<~INPUT
seeds: 79 14 55 13
seed-to-soil map:
50 98 2
52 50 48
soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15
fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4
water-to-light map:
88 18 7
18 25 70
light-to-temperature map:
45 77 23
81 45 19
68 64 13
temperature-to-humidity map:
0 69 1
1 0 69
humidity-to-location map:
60 56 37
56 93 4
INPUT
input = DATA.read
# class Range
# def overlaps?(other)
# cover?(other.first) || other.cover?(first)
# end
#
# def intersection(other)
# return nil if (self.max < other.begin or other.max < self.begin)
# [self.begin, other.begin].max..[self.max, other.max].min
# end
#
# alias_method :&, :intersection
# end
s, *maps = input.split(/\n\n/)
_, *seeds = s.split.map(&:to_i)
maps = maps.map do|m|
label, *rows = m.split(/\n/)
rows.map {_1.split.map(&:to_i)}
end
def find(page, target)
page.each do |ds, ss, len|
if target >= ss && target < ss + len
return ds + (target - ss)
end
end
target
end
seeds.map do |seed|
s = seed
maps.each do |m|
s = find(m, s)
end
s
end => r
p r.min
### PART 2
class Range
def overlaps?(other)
cover?(other.first) || other.cover?(first)
end
def intersection(other)
return nil if (self.max < other.begin or other.max < self.begin)
[self.begin, other.begin].max..[self.max, other.max].min
end
alias_method :&, :intersection
end
s, *maps = input.split(/\n\n/)
_, *seeds = s.split.map(&:to_i)
maps = maps.map do|m|
label, *rows = m.split(/\n/)
rows.map {_1.split.map(&:to_i)}
end
maps = maps.map do |m|
m2 = m.map do |dest, src, len|
[
src,
src + len - 1,
dest - src
]
end.sort
if m2[0][0] != 0
m2.unshift([0, m2[0][0] - 1, 0])
else
m2
end
end
maps = maps.map do |m|
src_end = m[-1][1]
end_cap = [
src_end + 1,
src_end + 1_000_000_000_000,
0
]
m + [end_cap]
end
def convert(page, seed_range)
page.filter_map do |src_start, src_end, diff|
if seed_range.overlaps?(src_start...src_end)
intersection = seed_range & (src_start...src_end)
rng_start = intersection.begin + diff
rng_end = intersection.end + diff
(rng_start..rng_end)
end
end
end
current_ranges = Array.new(8) { Set.new }
seeds.each_slice(2).map do |seed_start, len|
seed_range = (seed_start...seed_start + len)
current_ranges[0] << seed_range
maps.each_with_index do |m, i|
current_ranges[i].each do |current_range|
current_ranges[i + 1] += convert(m, current_range)
end
end
end
p current_ranges.last.map(&:min).min
time = %w(7 15 30).map(&:to_i)
distance = %w(9 40 200).map(&:to_i)
races = time.zip(distance)
races.reduce(1) do |acc, (time, record_distance)|
ways = 0
time.times do |seconds_held|
if seconds_held * (time - seconds_held) > record_distance
ways += 1
end
end
acc * ways
end => result
p result
input = <<~INPUT
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483
INPUT
data = input.each_line
# data = DATA.readlines
class Hand
include Comparable
attr_reader :name, :cards, :bid
def self.parse(line)
c, b = line.split
bid = b.to_i
cards = c.chars.map do |char|
{
"A" => 14,
"K" => 13,
"Q" => 12,
# "J" => 11,
"J" => 1,
"T" => 10,
}[char] || char.to_i
end
new(cards, c, bid)
end
def initialize(cards, name, bid)
@name = name
@cards = cards
@bid = bid
end
def <=>(other)
[type, cards] <=> [other.type, other.cards]
end
def type
joker_count = cards.count(1)
non_joker_cards = cards.select {|c| c != 1 }
card_counts = non_joker_cards
.group_by(&:itself)
.transform_values(&:count)
if card_counts.values.any? {|v| v == 5 }
5
elsif card_counts.values.any? {|v| v == 4 }
4 + joker_count
elsif card_counts.values.any? {|v| v == 3 } && card_counts.values.any? {|v| v == 2 }
3.5
elsif card_counts.values.any? {|v| v == 3 }
3 + joker_count
elsif card_counts.values.count {|v| v == 2 } == 2
2.5 + joker_count
elsif card_counts.values.any? {|v| v == 2 }
2 + joker_count
else
[1 + joker_count, 5].min
end
end
end
p data
.map { Hand.parse(_1) }
.sort
.map
.with_index {_1.bid * (_2 + 1)}
.sum
input = <<~INPUT
LR
11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)
INPUT
data = input.each_line.map(&:chomp)
data = DATA.readlines.map(&:chomp)
turns, _, *n = data
turns = turns.chars
nodes = n.map do |node|
name, left, right = node.scan(/\w+/)
[name, [left, right]]
end.to_h
def find(cur, nodes, turns, &blk)
step = 0
while !blk.call(cur)
if turns[step % turns.length] == "L"
cur, _ = nodes[cur]
else
_, cur = nodes[cur]
end
step += 1
end
step
end
p find("AAA", nodes, turns) {|c| c == "ZZZ"}
p nodes
.keys
.select {|k| k.end_with?("A") }
.map {|k| find(k, nodes, turns) {|c| c.end_with?("Z")} }
.inject(&:lcm)
input = <<~INPUT
0 3 6 9 12 15
1 3 6 10 15 21
10 13 16 21 30 45
INPUT
data = input.each_line
#data = DATA.readlines
stats = data.map do |line|
line.split.map(&:to_i)
end
def predict(stat)
layers = [stat]
until stat.all?(&:zero?)
stat = stat.each_cons(2).map { _2 - _1 }
layers.unshift(stat)
end
layers.each_cons(2) do |(p1, *, p2), nxt|
nxt.unshift(nxt.first - p1)
nxt.push(nxt.last + p2)
end
layers.last
end
# p stats.map {|stat| predict(stat)}.map(&:last).sum
p stats.map {|stat| predict(stat)}.map(&:first).sum
require 'set'
input = <<~INPUT
FF7FSF7F7F7F7F7F---7
L|LJ||||||||||||F--J
FL-7LJLJ||||||LJL-77
F--JF--7||LJLJ7F7FJ-
L---JF-JLJ.||-FJLJJ7
|F|F-JF---7F7-L7L|7|
|FFJF7L7F-JF7|JL---7
7-L-JL7||F7|L7F-7F7|
L.L7LFJ|||||FJL7||LJ
L7JLJL-JLJLJL--JLJ.L
INPUT
data = input.each_line.map(&:chomp)
data = DATA.readlines.map(&:chomp)
grid = data.map(&:chars)
start = nil
grid.each_with_index do |row, x|
row.each_with_index do |cell, y|
if cell == 'S'
start = [x, y]
end
end
end
p start
DIRS = {
"J" => [[-1, 0], [0, -1]],
"L" => [[-1, 0], [0, 1]],
"7" => [[0, -1], [1, 0]],
"F" => [[0, 1], [1, 0]],
"|" => [[-1, 0], [1, 0]],
"-" => [[0, -1], [0, 1]],
"." => [],
"S" => [],
}
def neighbors(grid, point)
x, y = point
pipe = grid[x][y]
DIRS[pipe].map do |dx, dy|
[x + dx, y + dy]
end
end
start_exits = []
[[-1, 0], [1, 0], [0, -1], [0, 1]].each do |dx, dy|
s_neighbor = [start[0] + dx, start[1] + dy]
if neighbors(grid, s_neighbor).include?(start)
start_exits << s_neighbor
end
end
start_point = start_exits.first
pipe = [start, start_point].to_set
while start_point != start_exits.last
ns = neighbors(grid, start_point)
ns.each do |neighbor|
if !pipe.include?(neighbor)
pipe << neighbor
start_point = neighbor
end
end
end
puts "Part 1, #{ pipe.size / 2 }"
clean_grid = Array.new(grid.size) { Array.new(grid.first.size, ' ') }
pipe.each do |(x, y)|
clean_grid[x][y] = grid[x][y]
end
inside_cells = []
inside = 0
clean_grid.each_with_index do |row, x|
print "#{ x }: "
row.each_with_index do |cell, y|
print cell
# skip cells on the pipeline
next if pipe.include?([x, y])
north = 0
south = 0
(y..row.size).each do |y2|
# Count north facing blockers (hacked S!)
if ["J", "L", "|", "S"].include?(clean_grid[x][y2])
north += 1
end
# Count south facing blockers (hacked S!)
if ["F", "7", "|", "S"].include?(clean_grid[x][y2])
south += 1
end
end
if [north, south].min.odd?
inside += 1
inside_cells << [x, y]
end
end
print "$\n"
end
p inside
input = <<~INPUT
...#......
.......#..
#.........
..........
......#...
.#........
.........#
..........
.......#..
#...#.....
INPUT
data = input.each_line.map(&:chomp)
data = DATA.readlines.map(&:chomp)
grid = data.map { |line| line.chars }
empty_rows = grid
.filter_map
.with_index do |row, x|
if row.all? { |cell| cell == '.' }
x
end
end
empty_cols = grid
.transpose
.filter_map
.with_index do |col, y|
if col.all? { |cell| cell == '.' }
y
end
end
galaxies = []
grid.each_with_index do |row, x|
row.each_with_index do |cell, y|
if cell == '#'
galaxies << [x, y]
end
end
end
def distance(a, b, empty_rows, empty_cols)
ax, ay = a
bx, by = b
d = (ax - bx).abs + (ay - by).abs
x_min, x_max = [ax, bx].minmax
y_min, y_max = [ay, by].minmax
(x_min...x_max).each do |x|
if empty_rows.include?(x)
# part 1
# d += 2 - 1
d += 1_000_000 - 1
end
end
(y_min...y_max).each do |y|
if empty_cols.include?(y)
d += 1_000_000 - 1
end
end
d
end
result = galaxies
.combination(2)
.inject(0) do |sum, (a, b)|
sum + distance(a, b, empty_rows, empty_cols)
end
p result
require 'rspec/autorun'
input = <<~INPUT
???.### 1,1,3
.??..??...?##. 1,1,3
?#?#?#?#?#?#?#? 1,3,1,6
????.#...#... 4,1,1
????.######..#####. 1,6,5
?###???????? 3,2,1
INPUT
def expand(record)
([record] * 5).join("?")
end
def parse2(line)
record, sizes = line.split(' ')
sizes = sizes.split(',').map(&:to_i) * 5
record = "#{expand(record)}.".gsub(/\.+/, '.')
[record, sizes]
end
def parse(line)
record, sizes = line.split(' ')
sizes = sizes.split(',').map(&:to_i)
record = "#{record}."
[record, sizes]
end
def count(record, sizes, group_size = 0, cache = {})
key = [record, sizes, group_size]
if cache[key]
return cache[key]
end
if sizes.any? { |s| s - group_size > record.length }
return cache[key] = 0
end
if sizes.empty?
if !record.include?("#")
return cache[key] = 1
else
return cache[key] = 0
end
end
current, *rest = record.chars
s = sizes.first
case [current, group_size]
in ['?', _]
# with both the `.` and the `#` instead of the question mark
return cache[key] = count('#' + rest.join, sizes, group_size, cache) + count('.' + rest.join, sizes, group_size, cache)
in ['#', _]
# keep moving forward through the group of brokens #
return cache[key] = count(rest.join, sizes, group_size + 1, cache)
in ['.', ^s]
# If we found the end of the group because group_size == sizes.first
return cache[key] = count(rest.join, sizes[1..], 0, cache)
in ['.', (1..)]
# Invalid group size / not enough #s
return cache[key] = 0
in ['.', 0]
# no-op - keep moving forward
return cache[key] = count(rest.join, sizes, 0, cache)
end
end
data = input.each_line
# data = DATA.readlines
puts "Record Count: #{data.count}"
counter = 0
data.inject(0) do |sum, line|
if counter % 5 == 0
puts counter
end
counter += 1
record, sizes = parse2(line)
sum + count(record, sizes)
end => r
puts "Part 2: #{r}"
describe 'count' do
it 'parses as expected' do
expect(parse2("???.### 1,1,3")).to eq([
"???.###????.###????.###????.###????.###.",
[1,1,3,1,1,3,1,1,3,1,1,3,1,1,3]
])
end
it 'works on basic cases' do
cases = [
["# 1", 1],
[".# 1", 1],
["#. 1", 1],
["#. 2", 0],
["???.### 1,1,3", 1],
[".??..??...?##. 1,1,3", 4],
["?#?#?#?#?#?#?#? 1,3,1,6", 1],
["????.#...#... 4,1,1", 1],
["????.######..#####. 1,6,5", 4],
["?###???????? 3,2,1", 10],
]
cases.each do |input, expected|
expect(count(*parse(input))).to eq(expected), -> { p input }
end
end
end
require 'rspec/autorun'
require 'byebug'
puts 'Hello, World!'
input = <<~INPUT
#.##..##.
..#.##.#.
##......#
##......#
..#.##.#.
..##..##.
#.#.##.#.
#...##..#
#....#..#
..##..###
#####.##.
#####.##.
..##..###
#....#..#
INPUT
def around(line, i)
return false if i == 0
left, right = line.partition.with_index {|x, j| j < i }
len = [left.length, right.length].min
return false if len == 0
left.reverse[0...len].zip(right[0...len]).count {|l, r| l != r } == 0
end
# Go through the first row and see if it has any lines of symetry
# check to see if those lines of symetry match any lines from the next
# row and narrow down to a smaller list.
def points_of_symetry(line)
(0...line.length).filter_map do |i|
if around(line, i)
i
end
end
end
def line_of_symetry(rows)
rows.map do |line|
points_of_symetry(line)
end.inject(&:&)
end
input = DATA.read
patterns = input.split("\n\n").map {|pat| pat.split("\n").map(&:chars)}
answer1 = 0
answer2 = 0
patterns.each_with_index do |pattern, k|
# Part 1
un_cols = line_of_symetry(pattern).first
un_rows = line_of_symetry(pattern.transpose).first
answer1 += un_cols.to_i + (un_rows.to_i * 100)
# Part 2
col_candidates = Set.new
row_candidates = Set.new
pattern.each_with_index do |row, i|
row.each_with_index do |col, j|
sub_pattern = pattern.map {|r| r.dup }
sub_pattern[i][j] = pattern[i][j] == '#' ? '.' : '#'
if cols = line_of_symetry(sub_pattern)
col_candidates += cols
end
if rows = line_of_symetry(sub_pattern.transpose)
row_candidates += rows
end
end
end
rcs = row_candidates - [un_rows]
ccs = col_candidates - [un_cols]
r = rcs.first
c = ccs.first
answer2 += c.to_i + (r.to_i * 100)
end
p "Part 1 answer: #{answer1}"
p "Part 2 answer: #{answer2}"
input = <<~INPUT
O....#....
O.OO#....#
.....##...
OO.#O....O
.O.....O#.
O.#..O.#.#
..O..#O..O
.......O..
#....###..
#OO..#....
INPUT
def weight(grid)
answer = 0
grid.each_with_index do |row, i|
row.each_with_index do |cell, j|
if cell == 'O'
answer += grid.length - i
end
end
end
answer
end
def roll(row)
swap = false
(row.length - 1).times do |i|
if row[i] == '.' && row[i + 1] == 'O'
row[i], row[i+1] = row[i+1], row[i]
swap = true
end
end
swap ? roll(row) : row
end
def tilt(grid)
grid.map { |row| roll(row.dup) }
end
def cycle(grid)
grid = grid.map(&:dup)
grid = n(grid)
grid = w(grid)
grid = s(grid)
grid = e(grid)
grid
end
def n(grid)
tilt(grid.transpose).transpose
end
def s(grid)
tilt(grid.reverse.transpose).transpose.reverse
end
def e(grid)
tilt(grid.map(&:reverse)).map(&:reverse)
end
def w(grid)
tilt(grid)
end
grid = input.each_line.map(&:chomp).map(&:chars)
#grid = DATA.readlines.map(&:chomp).map(&:chars)
pattern_counter = Hash.new{|h,k| h[k] = 0}
weights = {}
200.times do |n|
grid = cycle(grid.map(&:dup))
key = grid.join
pattern_counter[key] += 1
weights[n] = weight(grid)
break if pattern_counter[key] > 2
end
offset = pattern_counter.values.count(1)
cycle_length = pattern_counter.values.count {_1 > 1}
puts "offset: #{offset}"
puts "cycle_length: #{cycle_length}"
run_cycles = 1000000000
index = (run_cycles - offset - 1) % cycle_length
puts "Answer #{weights[offset + index]}"
def h(input)
input
.chars
.map(&:ord)
.inject(0) do |c, ascii|
c += ascii
c *= 17
c % 256
end
end
examples = [
["rn=1", 30],
["cm-", 253],
["qp=3", 97],
["cm=2", 47],
["qp-", 14],
["pc=4", 180],
["ot=9", 9],
["ab=5", 197],
["pc-", 48],
["pc=6", 214],
["ot=7", 231],
]
# data = "your input"
# instructions = data.split(",")
instructions = examples.map(&:first)
# Part 1
# instructions.map do |ins|
# h(ins)
# end.sum => r
# p r
boxes = Hash.new { |h, k| h[k] = [] }
def parse(ins)
*label, a, b = ins.chars
if b == "-"
[ins[0...-1], "-", nil]
else
[ins[0...-2], a, b.to_i]
end
end
instructions
.map {|ins| parse(ins)}
.each do |label, ins, focal|
lenses = boxes[h(label)]
if ins == "-"
lenses.delete_if {_1.first == label}
else
if lense = lenses.find {|(l, f)| l == label}
lense[1] = focal
else
lenses << [label, focal]
end
end
end
sum = 0
boxes.each do |k, lenses|
lenses.each_with_index do |(label, focal), slot|
sum += [k + 1, slot + 1, focal].inject(&:*)
end
end
p sum
input = File.read("./example")
input = File.read("./input")
grid = input.split("\n").map(&:chars)
DIR = {
right: [0, 1],
left: [0, -1],
up: [-1, 0],
down: [1, 0]
}
TURN = {
"/" => {
right: :up,
left: :down,
up: :right,
down: :left
},
"\\" => {
right: :down,
left: :up,
up: :left,
down: :right
}
}
def energy(beam, grid)
beams = [beam]
seen = Set.new
while !beams.empty?
x, y, direction = beams.shift
dx, dy = DIR[direction]
if x + dx < 0 || x + dx >= grid.size || y + dy < 0 || y + dy >= grid[0].size
next
end
new_beams = []
front = grid[x + dx][y + dy]
case [front, direction]
in ["|", :right | :left]
new_beams << [x + dx, y + dy, :up]
new_beams << [x + dx, y + dy, :down]
in ["-", :up | :down]
new_beams << [x + dx, y + dy, :left]
new_beams << [x + dx, y + dy, :right]
in ["/" | "\\", _]
new_beams << [x + dx, y + dy, TURN[front][direction]]
in [_, _]
new_beams << [x + dx, y + dy, direction]
end
new_beams.each do |beam|
beams << beam if !seen.include?(beam)
end
seen += beams
end
seen.map { |x, y, _| [x, y] }.to_set.length
end
max = 0
(0...grid.size).each do |x|
max = [energy([x, -1, :right], grid), max].max
max = [energy([x, grid.first.length, :left], grid), max].max
p max
end
(0...grid.first.size).each do |y|
max = [energy([-1, y, :down], grid), max].max
max = [energy([grid.length, y, :up], grid), max].max
p max
end
# grid.each_with_index do |row, x|
# row.each_with_index do |cell, y|
# if energized.include?([x, y])
# print "#"
# else
# print "."
# end
# end
# print "\n"
# end
require 'rspec/autorun'
require 'set'
require 'rb_heap'
input = <<~INPUT
2413432311323
3215453535623
3255245654254
3446585845452
4546657867536
1438598798454
4457876987766
3637877979653
4654967986887
4564679986453
1224686865563
2546548887735
4322674655533
INPUT
# input = <<~INPUT
# 1111
# 9991
# 9991
# 9991
# INPUT
DIRS = [
[0, 1],
[1, 0],
[0, -1],
[-1, 0],
]
def find(blocks, min_in_dir, max_in_dir)
# states = []
states = Heap.new {|a, b| (a <=> b) == -1 }
states << [0, 0, 0, 0, 1, 1] # heat, x, y, dx, dy, count_in_dir
states << [0, 0, 0, 1, 0, 1] # heat, x, y, dx, dy, count_in_dir
visited = Set.new
while !states.empty?
# Find the current state
# Slow:
# current = states.sort!.shift
current = states.pop
heat, x, y, dx, dy, count_in_dir = current
# Check if we've already been there
if visited.include?([x, y, dx, dy, count_in_dir])
next
end
visited << [x, y, dx, dy, count_in_dir]
nx, ny = x + dx, y + dy
# Make sure the new x and new y are on the grid
if nx < 0 || nx >= blocks.size || ny < 0 || ny >= blocks.first.size
next
end
new_heat = heat + blocks[nx][ny]
# Check if we've reached the end
if nx == blocks.length - 1 && ny == blocks.first.length - 1
puts "Found it!"
return new_heat
end
DIRS.each do |ddx, ddy|
# Don't go back the way we came
if [ddx + dx, ddy + dy] == [0, 0]
next
end
# If going the same direction, increase count in dir
if [ddx, ddy] == [dx, dy]
new_count_in_dir = count_in_dir + 1
else
if count_in_dir < min_in_dir
next
end
new_count_in_dir = 1
end
# If we've reached the max in dir, don't go that way
if new_count_in_dir > max_in_dir
next
end
states << [new_heat, nx, ny, ddx, ddy, new_count_in_dir]
end
end
end
# input = DATA.read
blocks = input
.split("\n")
.map {|line| line.chars.map(&:to_i) }
# p blocks
# p find(blocks, 1, 3)
p find(blocks, 4, 10)
# describe 'find' do
# it 'finds the shortest path without limitation' do
# blocks = [
# [1, 1, 1, 1],
# [9, 9, 9, 1],
# [9, 9, 9, 1],
# [9, 9, 9, 1],
# ]
# expect(find(blocks, 10)).to eq(6)
# end
#
# it 'finds the shortest path with max 2 in dir' do
# blocks = [
# [1, 1, 1, 1],
# [9, 9, 9, 1],
# [9, 9, 9, 1],
# [9, 9, 9, 1],
# ]
# expect(find(blocks, 2)).to eq(14)
# end
# end
input = <<~INPUT
R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)
INPUT
DIRS = {
"R" => [0, 1],
"L" => [0, -1],
"U" => [-1, 0],
"D" => [1, 0]
}
# 0 means R, 1 means D, 2 means L, and 3 means U.
DIRS2 = {
"0" => [0, 1],
"2" => [0, -1],
"3" => [-1, 0],
"1" => [1, 0]
}
input = DATA.read
instructions = input.split("\n").map do |line|
dir, steps, color = line.split(' ')
color = color.gsub(/[()#]/, '')
# PART 1
# steps = steps.to_i
# dir = DIRS[dir]
# PART 2
steps = color[0...5].to_i(16)
dir = DIRS2[color[5]]
[dir, steps]
end
# Array implementation which is too slow for part 2
# current = [0, 0]
# wall = []
#
# instructions.each do |dir, steps|
# steps.times do
# current = [current[0] + dir[0], current[1] + dir[1]]
# wall << current
# end
# end
#
# wall.each_cons(2).sum do |(x1, y1), (x2, y2)|
# x1 * y2 - x2 * y1
# end.abs.fdiv(2) => area
# puts area
# Enumerator implementation which is fast enough for part 2
def gen(instructions, counter)
current = [0, 0]
Enumerator.new do |enum|
instructions.each do |dir, steps|
steps.times do
counter.call
current = [current[0] + dir[0], current[1] + dir[1]]
enum << current
end
end
end
end
wall_length = 0
gen(instructions, -> { wall_length += 1 })
.each_cons(2) # shoelace formula
.sum do |(x1, y1), (x2, y2)|
x1 * y2 - x2 * y1
end
.abs
.fdiv(2) => area
puts area
# Pick's theorem
# A = i + (b / 2) - 1
# I = A - (b / 2) + 1
interior_points = area - (wall_length / 2) + 1
# Add the boundary points back in with the wall points
puts interior_points + wall_length
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment