#!/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