1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
garden = DATA.readlines(chomp: true).flat_map.with_index {|row, y|
  row.chars.map.with_index {|id, x| [[y,x], id] }
}.to_h

DELTAS = [[-1, 0], [1, 0], [0, -1], [0, 1]]

queue = Set[*garden.keys]
plots = [Set[queue.first]]
until queue.empty?
  yx = plots.last.select { queue.include?(_1) }.first
  if yx.nil?
    plots << Set[queue.first] if queue.first
    next
  end

  queue.delete(yx)

  id = garden.fetch(yx)
  neighbors = DELTAS
    .map {|d| yx.zip(d).map { _1 + _2 }}
    .select { garden.fetch(_1, nil) == id }
  if neighbors.empty?
    plots << Set[queue.first] if queue.first
  else
    plots.last.merge(neighbors)
  end
end

perimeter = ->(plot) {
  plot.flat_map {|yx|
    DELTAS
      .map {|delta| yx.zip(delta).map { _1 + _2 }}
      .reject { garden.fetch(_1, nil) == garden[plot.first] }
  }
}

pp plots
  .map { [_1.size, perimeter[_1]] }
  .sum { _1 * _2.length }

sides = ->(plot) {
  id = garden.fetch(plot.first)
  rows = plot.group_by(&:first).values.flat_map {|row|
    [-1, 1].flat_map {|dy|
      row
        .map {|y,x| [y+dy, x] }
        .reject { garden.fetch(_1, nil) == id }
        .map(&:last)
        .sort
        .chunk_while { _1 + 1 == _2 }
    }
  }
  cols = plot.group_by(&:last).values.flat_map {|row|
    [-1, 1].flat_map {|dx|
      row
        .map {|y,x| [y, x+dx] }
        .reject { garden.fetch(_1, nil) == id }
        .map(&:first)
        .sort
        .chunk_while { _1 + 1 == _2 }
    }
  }
  rows + cols
}

pp plots
  .map { [garden.fetch(_1.first), _1.size, sides[_1]] }
  .sum { _2 * _3.sum(&:count) }

__END__