Guest User

Untitled

a guest
Apr 21st, 2018
64
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.25 KB | None | 0 0
  1. module Rudoku # namespace power!
  2. class Sudoku
  3. attr_reader :fields
  4. attr_accessor :changed
  5.  
  6. def initialize
  7. @fields = Array.new(9 * 9) do |i|
  8. x = i % 9
  9. y = i / 9
  10. Field.new(self, x, y)
  11. end
  12. @changed = false
  13. end
  14.  
  15. def self.from_str(str)
  16. return if str.length != 81
  17. s = new
  18. (0..80).each do |i|
  19. s[i] = str[i].chr.to_i unless %w(_ x - 0).include? str[i].chr
  20. end
  21. s.changed = false
  22. return s
  23. end
  24.  
  25. def [](*val) # 1-based
  26. if val.size == 2 # 2, 3
  27. x = val[0] - 1
  28. y = val[1] - 1
  29. elsif val.size == 1 # B3
  30. if val[0].kind_of? Integer
  31. x = val[0] % 9
  32. y = val[0] / 9
  33. else
  34. str = val[0].to_s
  35. x = str[0] - ?A
  36. y = str[1] - ?1
  37. end
  38. else
  39. return nil
  40. end
  41.  
  42. @fields[x + y * 9]
  43. end
  44.  
  45. def []=(i, number) # also allows setting of multiple values: s[:B5] = [1, 2, 5]
  46. self[i].value = number
  47. end
  48.  
  49. def solved?
  50. @fields.all? { |f| f.solved? } && consistent?
  51. end
  52.  
  53. def changed?
  54. @changed
  55. end
  56.  
  57. def consistent?
  58. # This check will be run on an unsolved sudoku!
  59. # The result should be meaningful either way.
  60.  
  61. consistency_test = proc do |group|
  62. found_values = []
  63. (0..8).each do |i|
  64. v = group[i].value
  65. unless v.nil?
  66. return false if found_values.include?(v)
  67. found_values << v
  68. end
  69. end
  70. end
  71.  
  72. each_row(&consistency_test)
  73. each_column(&consistency_test)
  74. each_square(&consistency_test)
  75.  
  76. return true
  77. end
  78.  
  79. def to_s
  80. row_strings = (1..9).collect { |i| row_to_s(i) }
  81. row_strings.join("\n-+-+-+-+-+-+-+-+-\n")
  82. end
  83.  
  84. def row(i) # 1-based
  85. @fields[(9 * (i - 1))..(9 * (i - 1) + 8)]
  86. end
  87.  
  88. def column(i) # 1-based
  89. @fields.values_at(*(0..8).collect{ |j| 9 * j + i - 1})
  90. end
  91.  
  92. def square(i) # 1-based
  93. gx = (i - 1) % 3
  94. gy = (i - 1) / 3
  95. fs = []
  96. (0..2).each do |j|
  97. fs += @fields[(27*gy + 3*gx + 9*j)..(27*gy + 3*gx +9*j + 2)]
  98. end
  99. fs
  100. end
  101.  
  102. def row_to_s(i)
  103. (row(i).collect { |f| f.to_s }).join("|")
  104. end
  105.  
  106. def containing_square(i, j) # 1-based
  107. gx = (i - 1) / 3
  108. gy = (j - 1) / 3
  109. square(gx + 3*gy + 1)
  110. end
  111.  
  112. def each_row
  113. (1..9).each { |i| yield row(i) }
  114. end
  115.  
  116. def each_column
  117. (1..9).each { |i| yield column(i) }
  118. end
  119.  
  120. def each_square
  121. (1..9).each { |i| yield square(i) }
  122. end
  123.  
  124. def each_field
  125. (0..80).each { |i| yield self[i] }
  126. end
  127.  
  128.  
  129. def count_empty
  130. count = 0
  131. each_field { |f| count += 1 unless f.solved? }
  132. count
  133. end
  134.  
  135.  
  136. def best_force_candidate # find an empty field with the least possibilities
  137. return nil if solved?
  138.  
  139. best_candidate = nil
  140. best_count = 10 # 1 more than the maximum
  141.  
  142. each_field do |f|
  143. next if f.solved?
  144.  
  145. if f.possibilities.size < best_count
  146. best_candidate = f
  147. best_count = f.possibilities.size
  148. end
  149. end
  150.  
  151. best_candidate
  152. end
  153.  
  154.  
  155. def copy_data(source)
  156. @changed = source.changed
  157. source.each_field do |f|
  158. self[f.position].set_possibilities(f.possibilities)
  159. end
  160. end
  161. end
  162.  
  163. class Field
  164. attr_reader :possibilities, :x, :y, :sudoku
  165.  
  166. def initialize(sudoku, x, y)
  167. @sudoku = sudoku
  168. @possibilities = (1..9).to_a
  169. @x = x
  170. @y = y
  171. end
  172.  
  173. def remove(numbers)
  174. numbers = [*numbers] # this works for a single number and for ranges, too
  175. new_possibilities = @possibilities - numbers
  176.  
  177. if new_possibilities == @possibilities # no change
  178. return false
  179. elsif new_possibilities.size >= 1
  180. @possibilities = new_possibilities
  181. @sudoku.changed = true
  182. return true
  183. else
  184. return false # no change
  185. end
  186. end
  187.  
  188. def set_possibilities(p) # achterpoortje voor de bruteforcer (moet possibilities kunnen resetten)
  189. @possibilities = [*p].uniq
  190. end
  191.  
  192. def position
  193. (?A + x).chr + (y + 1).to_s # 2, 3 => B3
  194. end
  195.  
  196. def solved?
  197. @possibilities.size == 1
  198. end
  199.  
  200. def value
  201. solved? ? @possibilities[0] : nil
  202. end
  203.  
  204. def value=(number)
  205. remove((1..9).to_a - [*number])
  206. end
  207.  
  208. def to_s
  209. solved? ? value.to_s : " "
  210. end
  211. end
  212.  
  213. class Rule
  214. attr_reader :salience, :name
  215.  
  216. def initialize(name = nil, salience = 0, &block)
  217. @name = name
  218. @salience = salience
  219. @logic = block
  220. end
  221.  
  222. def apply_to(sudoku)
  223. args_iterate(@logic.arity - 1) do |*args|
  224. info = "#{name} (#{args.join(',')})"
  225. @logic.call(sudoku, *args)
  226. # puts success ? "APPLIED #{info}" : "SKIPPED #{info}"
  227. puts "APPLIED #{info}" if sudoku.changed?
  228.  
  229. sudoku.changed?
  230. end
  231. end
  232.  
  233. protected
  234. def args_iterate(n) # n: number of arguments
  235. if n == 0
  236. yield
  237. else
  238. # fill in leftmost parameter, iterate over the remaining argumnents
  239. args_iterate(n - 1) do |*args|
  240. ret = false
  241. (1..9).each do |i|
  242. ret = yield(i, *args)
  243. break if ret # breaks out of all
  244. end
  245.  
  246. ret # return value has to be passed on to the higher level
  247. end
  248. end
  249. end
  250. end
  251.  
  252. class Solver
  253. attr_reader :rules
  254. attr_reader :ara_count, :sra_count, :guess_count, :cg_count # stats
  255.  
  256. def initialize(rules = [])
  257. @rules = rules
  258. end
  259.  
  260. def sort_rules
  261. @rules = @rules.sort_by { |r| r.salience }.reverse # highest salience first
  262. end
  263.  
  264. def solve(sudoku, do_force = false)
  265. @ara_count = 0
  266. @sra_count = 0
  267. @guess_count = 0
  268. @cg_count = 0
  269. # if apply_rules returns true, no bruteforcing is required.
  270. if do_force
  271. solve_with_force(sudoku)
  272. else
  273. apply_rules(sudoku)
  274. end
  275. end
  276.  
  277. def solve_with_force(sudoku)
  278. return true if apply_rules(sudoku)
  279. return false unless sudoku.consistent? # applying the rules may have made the sudoku inconsistent. bubble up!
  280.  
  281. guess_and_solve(sudoku) ? sudoku.solved? : false
  282. end
  283.  
  284. def guess_and_solve(sudoku)
  285. backup = Sudoku.new
  286. backup.copy_data(sudoku)
  287.  
  288. field = sudoku.best_force_candidate
  289. possibilities = field.possibilities
  290.  
  291. trial_value = possibilities.first
  292. field.value = trial_value
  293. @guess_count += 1
  294. @cg_count += 1
  295.  
  296. puts "GUESSING #{field.position} = #{trial_value}"
  297.  
  298. # safety, might be obsolete, I dunno.
  299. return true if sudoku.solved?
  300.  
  301. success = solve_with_force(sudoku) # recursion, backtracking
  302. if success
  303. return true
  304. else
  305. # guess lead to inconsistency =>
  306. @cg_count -= 1 # guess wasn't correct after all!
  307. sudoku.copy_data(backup) # restore values
  308. field.set_possibilities(possibilities - [trial_value])
  309. return solve_with_force(sudoku) # try solving again, there's more info now (one less possibility!)
  310. end
  311. end
  312.  
  313. def apply_rules(sudoku)
  314. sort_rules
  315.  
  316. # iterate over all implemented rules in order of saliency
  317. # if one is applicable and changes the sudoku, start over
  318. @rules.each do |rule|
  319. rule.apply_to(sudoku)
  320. @ara_count += 1
  321.  
  322. if sudoku.changed? # something has changed -> restart rule application
  323. sudoku.changed = false
  324. @sra_count += 1
  325. retry
  326. end
  327. end
  328.  
  329. return sudoku.solved?
  330. end
  331. end
  332.  
  333.  
  334. def others(j)
  335. ([*(1..9)] - [j]).each { |i| yield i }
  336. end
  337. end
  338.  
  339.  
  340.  
  341. include Rudoku
  342.  
  343.  
  344.  
  345. #################### REGELS ####################
  346. # hebben een aantal parameters, de eerste is het sudoku-object, de andere lopen telkens van 1 tot 9
  347.  
  348. # BASISREGELS: enkelvoudige propagatie
  349. row_rule = Rule.new "row elimination", 100 do |sudoku, i, j|
  350. if sudoku[i,j].solved? # when a solved field occurs, remove its value from all other fields on the same row
  351. others(j) { |k| sudoku[i, k].remove(sudoku[i, j].value) } # iterate over columns, remove value
  352. end
  353. end
  354.  
  355. column_rule = Rule.new "column elimination", 90 do |sudoku, i, j|
  356. if sudoku[i, j].solved? # when a solved field occurs, remove its value from all other fields on the same column
  357. others(i) { |k| sudoku[k, j].remove(sudoku[i, j].value) } # iterate over rows, remove value
  358. end
  359. end
  360.  
  361. square_rule = Rule.new "square elimination", 80 do |sudoku, i, j|
  362. if sudoku[i, j].solved?
  363. square = sudoku.containing_square(i, j)
  364. square_index = ((i - 1) % 3) + 3 * ((j - 1) % 3)
  365. others(square_index) { |k| square[k - 1].remove(sudoku[i, j].value) }
  366. end
  367. end
  368.  
  369.  
  370. #################### TEST ####################
  371. metro = "--38----4-7--5461-2----7-5-9-532-7------8593-6--49----7--6---8--26-1----4-----1-2"
  372. manna_symmetrisch = "7-42-13-5---------3--5-7--26-5---8-3----7----4-3---9-72--9-5--1---------5-97-36-4"
  373. manna_tiseenacht = "2--81--46---6--1--1-7----2-------261-2-9-3-5-654-------1----6-4--3--6---48--79--5"
  374.  
  375. # su = Sudoku.from_str(manna_symmetrisch)
  376. su = Sudoku.new # leeg!
  377.  
  378. puts su.to_s
  379. puts "Empty fields: #{su.count_empty}"
  380. puts "Consistent: #{su.consistent?}"
  381.  
  382. puts "\n\n>> Solving now...\n"
  383. bruteSolver = Solver.new # empty ruleset -> bruteforce
  384. stupidSolver = Solver.new [row_rule, column_rule, square_rule]
  385.  
  386. so = stupidSolver
  387.  
  388. so.solve(su, true)
  389.  
  390. puts "\n\n" + su.to_s + "\n\n"
  391. puts "Empty fields: #{su.count_empty}"
  392. puts "Consistent: #{su.consistent?}"
  393. puts "\n\n>> SOLVED!" if su.solved?
  394. puts "#{so.ara_count} attempted rule applications"
  395. puts "#{so.sra_count} succesful rule applications"
  396. puts "#{so.guess_count} attempted guesses"
  397. puts "#{so.cg_count} correct guesses"
Add Comment
Please, Sign In to add comment