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
input = ARGF.readlines(chomp: true).map(&:chars)

# always tilts left
def tilt(platform)
  platform
    .map {|row| row.chunk_while { [_1, _2].tally.fetch(?#, 0).even? }}
    .map {|row| row.flat_map { _1.sort.reverse }}
end

def load(platform)
  platform.reverse
    .each.with_index
    .sum {|row, i| row.count(?O) * (i+1) }
end

# part one
p load(tilt(input.transpose).transpose)

def spin_cycle(platform)
  return enum_for(__method__, platform) unless block_given?

  loop do
    # north
    platform = tilt(platform.transpose).transpose

    # west
    platform = tilt(platform)

    # south
    platform = tilt(platform.reverse.transpose).transpose.reverse

    # east
    platform = tilt(platform.map(&:reverse)).map(&:reverse)

    yield platform
  end
end

# part two
spin = spin_cycle(input)
seen = {}
(1_000_000_000).times do |i|
  platform = spin.next

  if seen.has_key?(platform.hash)
    start, _ = seen.fetch(platform.hash)
    i = (1_000_000_000 - start) % (i+1 - start) + start
    p seen.find {|_,(j,_)| j == i }.fetch(1).fetch(1)
    exit
  end

  seen[platform.hash] = [i+1, load(platform)]
end