119 lines
2.8 KiB
Ruby
Executable file
119 lines
2.8 KiB
Ruby
Executable file
#!/usr/bin/env ruby
|
|
|
|
require "debug"
|
|
|
|
@input = (ARGV.first.nil? ? DATA : ARGF)
|
|
.readlines(chomp: true)
|
|
.map(&:chars)
|
|
|
|
@shapes = Hash.new { |h, k| h[k] = [] }
|
|
@area_counted = Hash.new
|
|
@perimeter_counted = Hash.new
|
|
|
|
def not_bounded?(point)
|
|
point.first < 0 ||
|
|
point.last < 0 ||
|
|
point.first >= @input.size ||
|
|
point.last >= @input.first.size
|
|
end
|
|
|
|
def counted?(point)
|
|
@area_counted[point]
|
|
end
|
|
|
|
def calculate_area(point, char, parent)
|
|
return 0 if counted?(point)
|
|
return 0 if not_bounded?(point)
|
|
return 0 if @input[point.first][point.last] != char
|
|
@shapes[[char, parent]] << point
|
|
@area_counted[point] = true
|
|
|
|
[
|
|
[point.first - 1, point.last],
|
|
[point.first + 1, point.last],
|
|
[point.first, point.last - 1],
|
|
[point.first, point.last + 1]
|
|
]
|
|
.map { calculate_area(_1, char, parent) }.sum + 1
|
|
end
|
|
|
|
def calculate_perimeter(point, char)
|
|
return 1 if @perimeter_counted[point] && @input.dig(*point) != char
|
|
return 0 if @perimeter_counted[point]
|
|
return 1 if not_bounded?(point)
|
|
return 1 if @input.dig(*point) != char
|
|
@perimeter_counted[point] = true
|
|
|
|
[
|
|
[point.first - 1, point.last],
|
|
[point.first + 1, point.last],
|
|
[point.first, point.last - 1],
|
|
[point.first, point.last + 1]
|
|
].map { calculate_perimeter(_1, char) }.sum
|
|
end
|
|
|
|
(0...@input.size).each do |x|
|
|
(0...@input.first.size).each do |y|
|
|
next if counted?([x, y])
|
|
char = @input.dig(x, y)
|
|
|
|
calculate_area([x, y], char, [x,y])
|
|
end
|
|
end
|
|
|
|
perimeters = Hash.new { |h, k| h[k] = [] }
|
|
(0...@input.size).each do |x|
|
|
(0...@input.first.size).each do |y|
|
|
next if @perimeter_counted[[x, y]]
|
|
char = @input.dig(x, y)
|
|
|
|
perimeters[[char, [x,y]]] = calculate_perimeter([x, y], char)
|
|
end
|
|
end
|
|
|
|
def sides(members)
|
|
char = @input.dig(*members.first)
|
|
|
|
min_x, max_x = members.map(&:first).min, members.map(&:first).max
|
|
min_y, max_y = members.map(&:last).min, members.map(&:last).max
|
|
|
|
sides = 0
|
|
(min_x..max_x).each do |x|
|
|
top_side = false
|
|
bot_side = false
|
|
(min_y..max_y).each do |y|
|
|
if top_side == false && members.include?([x,y]) && (x - 1 < 0 || !members.include?([x-1,y]))
|
|
top_side = true
|
|
sides += 1
|
|
elsif top_side == true && !members.include?([x,y])
|
|
top_side = false
|
|
elsif top_side == true && members.include?([x - 1, y])
|
|
top_side = false
|
|
end
|
|
|
|
if bot_side == false && members.include?([x,y]) && (x + 1 >= @input.size || !members.include?([x+1,y]))
|
|
bot_side = true
|
|
sides += 1
|
|
elsif bot_side == true && !members.include?([x,y])
|
|
bot_side = false
|
|
elsif bot_side == true && members.include?([x + 1, y])
|
|
bot_side = false
|
|
end
|
|
|
|
y += 1
|
|
end
|
|
end
|
|
|
|
sides * 2
|
|
end
|
|
|
|
p @shapes.keys.map { @shapes[_1].size * perimeters[_1] }.sum
|
|
p @shapes.map { |(key, members)| @shapes[key].size * sides(members) }.sum
|
|
|
|
__END__
|
|
AAAAAA
|
|
AAABBA
|
|
AAABBA
|
|
ABBAAA
|
|
ABBAAA
|
|
AAAAAA
|