class Sudoku
def self.solve(sheet)
new(sheet).solve.inspect_answers
end
def initialize(sheet)
@sheet = sheet.map(&:dup)
@answers = []
end
def to_s(sheet = @sheet)
sheet.map { |r| r.each_slice(3).map(&:join).join('|') }
.each_slice(3).map { |r| r.join("\n") }.join("\n---+---+---\n")
end
def inspect_answers
@answers.map { |a| to_s(a) }.join("\n\n")
end
def solve
pt = 0
fs = undetermineds.map { |r, c| Fiber.new { fiber(r, c) } }
space_count = fs.size
case pt
when -1 then return(self)
when space_count then @answers << @sheet.map(&:dup); pt -= 1
else pt += fs[pt].resume
end while 1
end
private
def undetermineds
9.times.each_with_object([]) do |r, ary|
9.times do |c|
ary << [r, c] if @sheet[r][c].zero?
end
end
end
def fiber(r, c)
prv = @sheet[r][c]
loop do
9.times do |i|
next unless disjoint?(r, c, i + 1)
@sheet[r][c] = i + 1
Fiber.yield(1)
end
@sheet[r][c] = prv
Fiber.yield(-1)
end
end
def disjoint?(r, c, n)
9.times.none? do |i|
@sheet[i][c] == n ||
@sheet[r][i] == n ||
@sheet[r - r % 3 + i % 3][c - c % 3 + i / 3] == n
end
end
end
puts Sudoku.solve($<.map{|i|i.split.map(&:to_i)})
CmNsYXNzIFN1ZG9rdQogIGRlZiBzZWxmLnNvbHZlKHNoZWV0KQogICAgbmV3KHNoZWV0KS5zb2x2ZS5pbnNwZWN0X2Fuc3dlcnMKICBlbmQKCiAgZGVmIGluaXRpYWxpemUoc2hlZXQpCiAgICBAc2hlZXQgPSBzaGVldC5tYXAoJjpkdXApCiAgICBAYW5zd2VycyA9IFtdCiAgZW5kCiAgCiAgZGVmIHRvX3Moc2hlZXQgPSBAc2hlZXQpCiAgICBzaGVldC5tYXAgeyB8cnwgci5lYWNoX3NsaWNlKDMpLm1hcCgmOmpvaW4pLmpvaW4oJ3wnKSB9CiAgICAgIC5lYWNoX3NsaWNlKDMpLm1hcCB7IHxyfCByLmpvaW4oIlxuIikgfS5qb2luKCJcbi0tLSstLS0rLS0tXG4iKQogIGVuZAogIAogIGRlZiBpbnNwZWN0X2Fuc3dlcnMKICAgIEBhbnN3ZXJzLm1hcCB7IHxhfCB0b19zKGEpIH0uam9pbigiXG5cbiIpCiAgZW5kCiAgCiAgZGVmIHNvbHZlCiAgICBwdCA9IDAKICAgIGZzID0gdW5kZXRlcm1pbmVkcy5tYXAgeyB8ciwgY3wgRmliZXIubmV3IHsgZmliZXIociwgYykgfSB9CiAgICBzcGFjZV9jb3VudCA9IGZzLnNpemUKICAgIGNhc2UgcHQKICAgIHdoZW4gLTEgICAgICAgICAgdGhlbiByZXR1cm4oc2VsZikKICAgIHdoZW4gc3BhY2VfY291bnQgdGhlbiBAYW5zd2VycyA8PCBAc2hlZXQubWFwKCY6ZHVwKTsgcHQgLT0gMQogICAgZWxzZSAgICAgICAgICAgICAgICAgIHB0ICs9IGZzW3B0XS5yZXN1bWUKICAgIGVuZCB3aGlsZSAxCiAgZW5kCiAgCiAgcHJpdmF0ZQogIAogIGRlZiB1bmRldGVybWluZWRzCiAgICA5LnRpbWVzLmVhY2hfd2l0aF9vYmplY3QoW10pIGRvIHxyLCBhcnl8CiAgICAgIDkudGltZXMgZG8gfGN8CiAgICAgICAgYXJ5IDw8IFtyLCBjXSBpZiBAc2hlZXRbcl1bY10uemVybz8KICAgICAgZW5kCiAgICBlbmQKICBlbmQKICAKICBkZWYgZmliZXIociwgYykKICAgIHBydiA9IEBzaGVldFtyXVtjXQogICAgbG9vcCBkbwogICAgICA5LnRpbWVzIGRvIHxpfAogICAgICAgIG5leHQgdW5sZXNzIGRpc2pvaW50PyhyLCBjLCBpICsgMSkKICAgICAgICBAc2hlZXRbcl1bY10gPSBpICsgMQogICAgICAgIEZpYmVyLnlpZWxkKDEpCiAgICAgIGVuZAogICAgICBAc2hlZXRbcl1bY10gPSBwcnYKICAgICAgRmliZXIueWllbGQoLTEpCiAgICBlbmQKICBlbmQKICAKICBkZWYgZGlzam9pbnQ/KHIsIGMsIG4pCiAgICA5LnRpbWVzLm5vbmU/IGRvIHxpfAogICAgICBAc2hlZXRbaV1bY10gPT0gbiB8fAogICAgICBAc2hlZXRbcl1baV0gPT0gbiB8fAogICAgICBAc2hlZXRbciAtIHIgJSAzICsgaSAlIDNdW2MgLSBjICUgMyArIGkgLyAzXSA9PSBuCiAgICBlbmQKICBlbmQKZW5kCgpwdXRzIFN1ZG9rdS5zb2x2ZSgkPC5tYXB7fGl8aS5zcGxpdC5tYXAoJjp0b19pKX0pCg==
MSAyIDMgIDQgNSA2ICA3IDggOQo0IDUgNiAgNyA4IDkgIDEgMiAzCjcgOCA5ICAxIDIgMyAgNCA1IDYKMiAzIDEgIDkgNyA1ICBfIF8gXwo1IDYgNCAgOCBfIF8gIF8gXyBfCjggOSA3ICA2IF8gMSAgXyBfIF8KMyAxIDIgIF8gXyBfICBfIF8gXwo2IDQgNSAgXyBfIF8gIF8gXyBfCjkgNyA4ICBfIF8gXyAgXyBfIF8=
1 2 3 4 5 6 7 8 9
4 5 6 7 8 9 1 2 3
7 8 9 1 2 3 4 5 6
2 3 1 9 7 5 _ _ _
5 6 4 8 _ _ _ _ _
8 9 7 6 _ 1 _ _ _
3 1 2 _ _ _ _ _ _
6 4 5 _ _ _ _ _ _
9 7 8 _ _ _ _ _ _