# frozen_string_literal: true
class Slide
attr_accessor :commands
def initialize(field, commands)
commands = optimise(commands.gsub(/[LR]+(?<m>[LR])|[UD]+(?<m>[UD])/, '\k<m>'))
@commands = {
initial: commands[0].to_s,
direction: ('LDRUL'.include?(commands[0, 2]) ? :+ : :-),
size: commands.size
}
@angle = ('ULDR'.index(@commands[:initial]) - (clockwise? ? 1 : 0) ) % 4
@field = normalize(field)
@width = field.index(/\n|\z/)
@height = @field.size
@initial_state = Marshal.load(Marshal.dump(@field)).freeze
end
def to_s
return @field.map(&:join).join($/) if @commands[:size] < 2
ary = @field.map { |s| s.ljust(@width, ?.).split('') }
upright(ary)
ary.map(&:join).join($/)
end
def upright(ary)
return if @angle == 0
ary.reverse! if @angle <= 2
ary.each(&:reverse!) if @angle >= 2
end
def clockwise?
@commands[:direction] == :-
end
def optimise(dirs)
nil while dirs.gsub!(/(?<m>.).\k<m>(?=.)|(?<m>(?<n>.).)\k<n>\z/, '\k<m>')
dirs
end
def normalize(field)
ary = field.split.map { |s| s.split('') }
f = ->(a) { a.select { |c| c != ?. }.join }
return ary if @commands[:size] == 0
incline_ary(ary, @commands[:initial])
return ary if @commands[:size] == 1
rotate_ary(ary, clockwise? ? :+ : :-)
incline_ary(ary, @commands[:initial])
rotate_ary(ary, @commands[:direction])
upright(ary)
ary.map(&f)
end
def incline_ary(ary, direction)
rotate_ary(ary, :+) if %w[U D].include?(direction)
f = case direction
when ?R, ?D then ->c{c == ?.}
when ?L, ?U then ->c{c != ?.}
end
ary.replace(ary.map { |a| a.partition(&f).flatten })
%w[U D].include?(direction) ? rotate_ary(ary, :-) : ary
end
def rotate_ary(ary, dir = :+)
a = ary.transpose
case dir
when :+ then ary.replace(a.reverse)
when :- then ary.replace(a.map(&:reverse))
end
end
def incline_all(count = @commands[:size] - 2)
f = %w[L R].include?(@commands[:initial]) ? ->(a) { a.even? } : ->(a) { a.odd? }
angle = clockwise? ? -1 : 1
count.times do |i|
f.call(i) ? inversion_h : inversion_v
@angle = (@angle + angle) % 4
break(incline_all((count - i - 1) % (i + 1))) if @field == @initial_state
end
end
def inversion_h
@field.map(&:reverse!)
end
def inversion_v
verticals = @width.times.map { |i| @field.map { |s| s[i] }.compact }
@field = @height.times.map { |i| verticals.map { |s| s[~i]}.join }
end
end
samples = [[<<~EOT, ''], [<<~EOT, 'U'], [<<~EOT, 'UR'], [<<~EOT, 'LRRLLUDRRLRLRRURRRRLLLRULDUDULDLLRDULURULUDLDLUDDL'], [<<~EOT, 'LURD'*10**6]]
...
.a.
...
EOT
...
...
.a.
EOT
...
.a.
...
EOT
.....
.d.i.
kmegk
..tu.
EOT
.....
.d.i.
kmegk
..tu.
EOT
samples.each do |field, commands|
pre = Time.now
s = Slide.new(field, commands)
s.incline_all
singular = commands.size == 1 ? ['', 'is'] : ['s', 'are']
puts "%s\n%d command%s (%s%s) %s reduced to %d\n%s=>\n%s\n# %f sec" % [
?-*70, commands.size, singular.first, commands[0, 20],
commands.size > 20 ? '...' : '', singular.last,
s.commands[:size], field, s, Time.now - pre
]
end
IyBmcm96ZW5fc3RyaW5nX2xpdGVyYWw6IHRydWUKCmNsYXNzIFNsaWRlCiAgYXR0cl9hY2Nlc3NvciA6Y29tbWFuZHMKICAKICBkZWYgaW5pdGlhbGl6ZShmaWVsZCwgY29tbWFuZHMpCiAgICBjb21tYW5kcyAgPSBvcHRpbWlzZShjb21tYW5kcy5nc3ViKC9bTFJdKyg/PG0+W0xSXSl8W1VEXSsoPzxtPltVRF0pLywgJ1xrPG0+JykpCiAgICBAY29tbWFuZHMgPSB7CiAgICAgIGluaXRpYWw6ICAgY29tbWFuZHNbMF0udG9fcywKICAgICAgZGlyZWN0aW9uOiAoJ0xEUlVMJy5pbmNsdWRlPyhjb21tYW5kc1swLCAyXSkgPyA6KyA6IDotKSwKICAgICAgc2l6ZTogICAgICBjb21tYW5kcy5zaXplCiAgICB9CiAgICBAYW5nbGUgICAgPSAoJ1VMRFInLmluZGV4KEBjb21tYW5kc1s6aW5pdGlhbF0pIC0gKGNsb2Nrd2lzZT8gPyAxIDogMCkgKSAlIDQKICAgIEBmaWVsZCAgICA9IG5vcm1hbGl6ZShmaWVsZCkKICAgIEB3aWR0aCAgICA9IGZpZWxkLmluZGV4KC9cbnxcei8pCiAgICBAaGVpZ2h0ICAgPSBAZmllbGQuc2l6ZQogICAgQGluaXRpYWxfc3RhdGUgPSBNYXJzaGFsLmxvYWQoTWFyc2hhbC5kdW1wKEBmaWVsZCkpLmZyZWV6ZQogIGVuZAogIAogIGRlZiB0b19zCiAgICByZXR1cm4gQGZpZWxkLm1hcCgmOmpvaW4pLmpvaW4oJC8pIGlmIEBjb21tYW5kc1s6c2l6ZV0gPCAyCiAgICBhcnkgPSBAZmllbGQubWFwIHsgfHN8IHMubGp1c3QoQHdpZHRoLCA/Likuc3BsaXQoJycpIH0KICAgIHVwcmlnaHQoYXJ5KQogICAgYXJ5Lm1hcCgmOmpvaW4pLmpvaW4oJC8pCiAgZW5kCiAgCiAgZGVmIHVwcmlnaHQoYXJ5KQogICAgcmV0dXJuIGlmIEBhbmdsZSA9PSAwCiAgICBhcnkucmV2ZXJzZSEgaWYgQGFuZ2xlIDw9IDIKICAgIGFyeS5lYWNoKCY6cmV2ZXJzZSEpIGlmIEBhbmdsZSA+PSAyCiAgZW5kCiAgCiAgZGVmIGNsb2Nrd2lzZT8KICAgIEBjb21tYW5kc1s6ZGlyZWN0aW9uXSA9PSA6LQogIGVuZAogIAogIGRlZiBvcHRpbWlzZShkaXJzKQogICAgbmlsIHdoaWxlIGRpcnMuZ3N1YiEoLyg/PG0+LikuXGs8bT4oPz0uKXwoPzxtPig/PG4+LikuKVxrPG4+XHovLCAnXGs8bT4nKQogICAgZGlycwogIGVuZAogIAogIGRlZiBub3JtYWxpemUoZmllbGQpCiAgICBhcnkgPSBmaWVsZC5zcGxpdC5tYXAgeyB8c3wgcy5zcGxpdCgnJykgfQogICAgZiA9IC0+KGEpIHsgYS5zZWxlY3QgeyB8Y3wgYyAhPSA/LiB9LmpvaW4gfQogICAgcmV0dXJuIGFyeSAgaWYgQGNvbW1hbmRzWzpzaXplXSA9PSAwCiAgICBpbmNsaW5lX2FyeShhcnksIEBjb21tYW5kc1s6aW5pdGlhbF0pCiAgICByZXR1cm4gYXJ5IGlmIEBjb21tYW5kc1s6c2l6ZV0gPT0gMQogICAgcm90YXRlX2FyeShhcnksIGNsb2Nrd2lzZT8gPyA6KyA6IDotKQogICAgaW5jbGluZV9hcnkoYXJ5LCBAY29tbWFuZHNbOmluaXRpYWxdKQogICAgcm90YXRlX2FyeShhcnksIEBjb21tYW5kc1s6ZGlyZWN0aW9uXSkKICAgIHVwcmlnaHQoYXJ5KQogICAgYXJ5Lm1hcCgmZikKICBlbmQKICAKICBkZWYgaW5jbGluZV9hcnkoYXJ5LCBkaXJlY3Rpb24pCiAgICByb3RhdGVfYXJ5KGFyeSwgOispIGlmICV3W1UgRF0uaW5jbHVkZT8oZGlyZWN0aW9uKQogICAgZiA9IGNhc2UgZGlyZWN0aW9uCiAgICAgICAgd2hlbiA/UiwgP0QgdGhlbiAtPmN7YyA9PSA/Ln0KICAgICAgICB3aGVuID9MLCA/VSB0aGVuIC0+Y3tjICE9ID8ufQogICAgICAgIGVuZAogICAgYXJ5LnJlcGxhY2UoYXJ5Lm1hcCB7IHxhfCBhLnBhcnRpdGlvbigmZikuZmxhdHRlbiB9KQogICAgJXdbVSBEXS5pbmNsdWRlPyhkaXJlY3Rpb24pID8gcm90YXRlX2FyeShhcnksIDotKSA6IGFyeQogIGVuZAogIAogIGRlZiByb3RhdGVfYXJ5KGFyeSwgZGlyID0gOispCiAgICBhID0gYXJ5LnRyYW5zcG9zZQogICAgY2FzZSBkaXIKICAgIHdoZW4gOisgdGhlbiBhcnkucmVwbGFjZShhLnJldmVyc2UpCiAgICB3aGVuIDotIHRoZW4gYXJ5LnJlcGxhY2UoYS5tYXAoJjpyZXZlcnNlKSkKICAgIGVuZAogIGVuZAogIAogIGRlZiBpbmNsaW5lX2FsbChjb3VudCA9IEBjb21tYW5kc1s6c2l6ZV0gLSAyKQogICAgZiA9ICV3W0wgUl0uaW5jbHVkZT8oQGNvbW1hbmRzWzppbml0aWFsXSkgPyAtPihhKSB7IGEuZXZlbj8gfSA6IC0+KGEpIHsgYS5vZGQ/IH0KICAgIGFuZ2xlID0gY2xvY2t3aXNlPyA/IC0xIDogMSAKICAgIGNvdW50LnRpbWVzIGRvIHxpfAogICAgICBmLmNhbGwoaSkgPyBpbnZlcnNpb25faCA6IGludmVyc2lvbl92CiAgICAgIEBhbmdsZSA9IChAYW5nbGUgKyBhbmdsZSkgJSA0CiAgICAgIGJyZWFrKGluY2xpbmVfYWxsKChjb3VudCAtIGkgLSAxKSAlIChpICsgMSkpKSBpZiBAZmllbGQgPT0gQGluaXRpYWxfc3RhdGUKICAgIGVuZAogIGVuZAogIAogIGRlZiBpbnZlcnNpb25faAogICAgQGZpZWxkLm1hcCgmOnJldmVyc2UhKQogIGVuZAogIAogIGRlZiBpbnZlcnNpb25fdgogICAgdmVydGljYWxzID0gQHdpZHRoLnRpbWVzLm1hcCB7IHxpfCBAZmllbGQubWFwIHsgfHN8IHNbaV0gfS5jb21wYWN0IH0KICAgIEBmaWVsZCA9IEBoZWlnaHQudGltZXMubWFwIHsgfGl8IHZlcnRpY2Fscy5tYXAgeyB8c3wgc1t+aV19LmpvaW4gfQogIGVuZAplbmQKCnNhbXBsZXMgPSBbWzw8fkVPVCwgJyddLCBbPDx+RU9ULCAnVSddLCBbPDx+RU9ULCAnVVInXSwgWzw8fkVPVCwgJ0xSUkxMVURSUkxSTFJSVVJSUlJMTExSVUxEVURVTERMTFJEVUxVUlVMVURMRExVRERMJ10sIFs8PH5FT1QsICdMVVJEJyoxMCoqNl1dCiAgLi4uCiAgLmEuCiAgLi4uCkVPVAogIC4uLgogIC4uLgogIC5hLgpFT1QKICAuLi4KICAuYS4KICAuLi4KRU9UCiAgLi4uLi4KICAuZC5pLgogIGttZWdrCiAgLi50dS4KRU9UCiAgLi4uLi4KICAuZC5pLgogIGttZWdrCiAgLi50dS4KRU9UCgpzYW1wbGVzLmVhY2ggZG8gfGZpZWxkLCBjb21tYW5kc3wKICBwcmUgPSBUaW1lLm5vdwogIHMgPSBTbGlkZS5uZXcoZmllbGQsIGNvbW1hbmRzKQogIHMuaW5jbGluZV9hbGwKICBzaW5ndWxhciA9IGNvbW1hbmRzLnNpemUgPT0gMSA/IFsnJywgJ2lzJ10gOiBbJ3MnLCAnYXJlJ10KICBwdXRzICIlc1xuJWQgY29tbWFuZCVzICglcyVzKSAlcyByZWR1Y2VkIHRvICVkXG4lcz0+XG4lc1xuIyAlZiBzZWMiICUgWwogICAgPy0qNzAsIGNvbW1hbmRzLnNpemUsIHNpbmd1bGFyLmZpcnN0LCBjb21tYW5kc1swLCAyMF0sCiAgICBjb21tYW5kcy5zaXplID4gMjAgPyAnLi4uJyA6ICcnLCBzaW5ndWxhci5sYXN0LAogICAgcy5jb21tYW5kc1s6c2l6ZV0sIGZpZWxkLCBzLCBUaW1lLm5vdyAtIHByZQogIF0KZW5kCg==