fork download
  1. # encoding: utf-8
  2. #
  3. # The problems other than the first are cited from;
  4. # www.nikoli.com/ja/puzzles/numberlink
  5. #
  6.  
  7. module Solver
  8. def solve
  9. previous_state, present_state = nil, dump
  10. while !solved? && present_state != previous_state
  11. previous_state = present_state
  12. step
  13. present_state = dump
  14. end
  15. self
  16. end
  17.  
  18. def solved?
  19. all?(&:fixed?)
  20. end
  21.  
  22. def step
  23. each(&:solve)
  24. end
  25.  
  26. def reductio_ad_absurdum
  27. each(&:check_cell_type_error)
  28. while !solve.solved?
  29. previous_state = dump
  30. cell = reject(&:directed?)[0]
  31. candidate = cell.connectable_cells[0]
  32. begin
  33. cell.disconnect(candidate)
  34. reductio_ad_absurdum
  35. rescue NumberLink::Cell::CellTypeError
  36. restore(previous_state)
  37. cell_at(*cell.pos).connect(cell_at(*candidate.pos))
  38. end
  39. end
  40. self
  41. end
  42. end
  43.  
  44.  
  45.  
  46. module CellInteraction
  47. CellTypeError = Class.new(StandardError)
  48.  
  49. def cell_at(x, y)
  50. return unless (0...@width) === x && (0...@height) === y
  51. @field[y][x]
  52. end
  53.  
  54. def disjoint?(other)
  55. (@marks & other.marks).empty?
  56. end
  57.  
  58. # Returns nil if and only if other is not next to self.
  59. def relative_position(other)
  60. if pos_x == other.pos_x
  61. case pos_y - other.pos_y
  62. when -1 then [:down, :up]
  63. when 1 then [:up, :down]
  64. end
  65. elsif pos_y == other.pos_y
  66. case pos_x - other.pos_x
  67. when -1 then [:right, :left]
  68. when 1 then [:left, :right]
  69. end
  70. end
  71. end
  72.  
  73. def connectable?(other)
  74. return false if fixed? || other.fixed?
  75. rp, * = relative_position(other)
  76. rp && !@disconnection.include?(rp) && !@connection.include?(rp)
  77. end
  78.  
  79. def connected?(other)
  80. other, * = relative_position(other) if self.class == other
  81. other && @connection.include?(other)
  82. end
  83.  
  84. def disconnected?(other)
  85. other, * = relative_position(other) if self.class === other
  86. other && @disconnection.include?(other)
  87. end
  88.  
  89. def connect(other)
  90. return if fixed? || other.fixed?
  91. rp = relative_position(other)
  92. @connection |= [rp[0]]
  93. other.connection |= [rp[1]]
  94. chain = chained_cells
  95. common_marks = @marks & other.marks
  96. other.marks.replace(common_marks)
  97. chained_cells.each do |cell|
  98. next if cell.fixed?
  99. cell.marks = other.marks
  100. cell.fix if cell.fixable?
  101. end
  102. content_connection
  103. other.content_connection
  104. end
  105.  
  106. def disconnect(other)
  107. return if fixed? || other.fixed?
  108. rp = relative_position(other)
  109. @disconnection |= [rp[0]]
  110. other.disconnection |= [rp[1]]
  111. @marks.delete(other.marks[0]) if other.identified?
  112. other.marks.delete(@marks[0]) if identified?
  113. content_connection
  114. other.content_connection
  115. end
  116.  
  117. def content_connection
  118. return fix if fixed? || fixable?
  119. param = edge? ? 1 : 0
  120. if @connection.size == 2 - param
  121. rest_directions.each{|dir| disconnect(adjacent_cell(dir))}
  122. elsif @disconnection.size == 2 + param
  123. rest_directions.each{|dir| connect(adjacent_cell(dir))}
  124. end
  125. fix if fixable?
  126. end
  127.  
  128. def connectable_cells
  129. x, y = pos
  130. [cell_at(x - 1, y), cell_at(x, y - 1),
  131. cell_at(x + 1, y), cell_at(x, y + 1)].select do |other|
  132. other && connectable?(other)
  133. end
  134. end
  135.  
  136. def adjacent?(other)
  137. !!relative_position(other)
  138. end
  139.  
  140. def adjacent_cell(*directions)
  141. x, y = pos
  142. x -= 1 if directions.include?(:left)
  143. y -= 1 if directions.include?(:up)
  144. x += 1 if directions.include?(:right)
  145. y += 1 if directions.include?(:down)
  146. cell_at(x, y)
  147. end
  148.  
  149. def chained_cells(direction = nil)
  150. [self] + (@connection - [direction]).flat_map do |dir|
  151. ac = adjacent_cell(dir)
  152. ac.check_cell_type_error
  153. ac.chained_cells(opposite_direction(dir))
  154. end
  155. end
  156.  
  157. def adjacent_chains?(chain1, chain2)
  158. chain1.any? do |cell1|
  159. chain2.any? do |cell2|
  160. cell1.adjacent?(cell2) && cell1.disconnected?(cell2)
  161. end
  162. end
  163. end
  164.  
  165. def check_cell_type_error
  166. broken = @connection.size + @disconnection.size > 4 ||
  167. edge? && @connection.size > 1 ||
  168. !edge? && @disconnection.size > 2
  169. raise CellTypeError, 'Connection or Disconnection is broken' if broken
  170. raise CellTypeError, 'No Marks in a cell' if @marks.empty?
  171. detouring = bended? &&
  172. @connection.any? do |dir|
  173. ac = adjacent_cell(dir)
  174. ac.bended? &&
  175. !(@connection & ac.connection).empty?
  176. end
  177. raise CellTypeError, 'Detouring' if detouring
  178. end
  179. end
  180.  
  181.  
  182.  
  183. module CellSolver
  184. def solve
  185. disconnect_with_inside_of_corner ||
  186. disconnect_with_disjoint_cells ||
  187. connect_with_same_marked_cells ||
  188. connect_when_no_alternatives ||
  189. disconnect_with_adjacent_chains ||
  190. reduce_marks ||
  191. deduce_from_slanting_cells ||
  192. false
  193. end
  194.  
  195. def disconnect_with_disjoint_cells
  196. connectable_cells.each{|other| disconnect(other) if disjoint?(other)}
  197. directed?
  198. end
  199.  
  200. def connect_with_same_marked_cells
  201. return unless identified?
  202. connectable_cells.each{|other| connect(other) if @marks == other.marks}
  203. directed?
  204. end
  205.  
  206. def connect_when_no_alternatives
  207. connectables = connectable_cells
  208. param = edge? ? 1 : 2
  209. connectables.each{|cell| connect(cell)} if @connection.size + connectables.size == param
  210. directed?
  211. end
  212.  
  213. def disconnect_with_inside_of_corner
  214. return unless bended?
  215. inside_cell = adjacent_cell(*@connection)
  216. @connection.each{|dir| inside_cell.disconnect(adjacent_cell(dir))}
  217. end
  218.  
  219. def disconnect_with_adjacent_chains
  220. chain = chained_cells
  221. connectable_cells.each do |other|
  222. disconnect(other) if adjacent_chains?(chain, other.chained_cells)
  223. end
  224. directed?
  225. end
  226.  
  227. def reduce_marks
  228. return if edge? || !@connection.empty?
  229. connectables = connectable_cells
  230. identified = connectables.select(&:identified?)
  231. identified_marks = identified.map(&:marks)
  232. major_mark = identified_marks.find{|m| identified_marks.count(m) == 2}
  233. cs, is = connectables.size, identified.size
  234. if major_mark && (cs == 3 || is == 4 && identified_marks.uniq.size == 3)
  235. identified.each{|cell| connect(cell) if cell.marks == major_mark}
  236. elsif !major_mark && (cs == 4 && is == 3 || cs == 3 && is == 2)
  237. @marks.replace(@marks & identified_marks.reduce(:|))
  238. check_cell_type_error
  239. connect((connectables - identified)[0])
  240. end
  241. directed?
  242. end
  243.  
  244. def deduce_from_slanting_cells
  245. return if edge?
  246. slanting_ways = [[:left, :up], [:left, :down], [:right, :up], [:right, :down]]
  247. slanting_ways.each{|dir| deduce_one_slanting_way(dir)}
  248. directed?
  249. end
  250.  
  251. def deduce_one_slanting_way(directions)
  252. [directions, directions.reverse].each do |dir1, dir2|
  253. next if !disconnected?(opposite_direction(dir1)) ||
  254. connected?(opposite_direction(dir2))
  255. sc = self
  256. deduction = true
  257. while sc = sc.adjacent_cell(dir1, dir2)
  258. break(deduction = false) if sc.edge?
  259. break if sc.disconnected?(dir2)
  260. end
  261. connect(adjacent_cell(opposite_direction(dir2))) if deduction
  262. end
  263. end
  264.  
  265. def opposite_direction(direction)
  266. case direction
  267. when :left then :right
  268. when :up then :down
  269. when :right then :left
  270. when :down then :up
  271. end
  272. end
  273. end
  274.  
  275.  
  276.  
  277. class NumberLink
  278. include Solver
  279. include Enumerable
  280.  
  281. NULL_MARK = '*'
  282. TypeError = Class.new(StandardError)
  283.  
  284. def initialize(puzzle_sheet)
  285. puzzle_ary = puzzle_sheet.split($/).map(&:split)
  286. @height = puzzle_ary.size
  287. @width = puzzle_ary[0].size
  288. all_marks = puzzle_sheet.split.uniq.sort - [NULL_MARK]
  289. @field = []
  290. @field.concat(
  291. puzzle_ary.map.with_index do |ary, y|
  292. ary.map.with_index do |mark, x|
  293. default_cell(x, y, mark, all_marks)
  294. end
  295. end
  296. )
  297. check_type_error
  298. self
  299. end
  300.  
  301. def default_cell(x, y, mark, all_marks)
  302. disconnection = []
  303. disconnection << :left if x == 0
  304. disconnection << :right if x == @width - 1
  305. disconnection << :up if y == 0
  306. disconnection << :down if y == @height - 1
  307. default_marks = mark == NULL_MARK ? Array.new(all_marks) : [mark]
  308. Cell.new([x, y], default_marks, mark != NULL_MARK,
  309. disconnection, @field, @width, @height)
  310. end
  311.  
  312. def check_type_error
  313. raise TypeError, 'Field is not rectangle' if @field.any?{|ary| ary.size != @width}
  314. all_marks = each_with_object([]){|cell, m| m << cell.marks[0] if cell.edge?}
  315. not_paired_marks = all_marks.uniq.select{|m| all_marks.count(m) != 2}
  316. return if not_paired_marks.empty?
  317. msg = not_paired_marks.size == 1 ?
  318. 'There is a not paired mark: %p' :
  319. 'There are not paired marks: %p'
  320. raise TypeError, msg % [not_paired_marks]
  321. end
  322.  
  323. def each
  324. @field.each{|ary| ary.each{|cell| yield cell}}
  325. end
  326.  
  327. def to_s
  328. @field.map(&:join).join($/)
  329. end
  330.  
  331. def cell_at(x, y)
  332. return unless (0...@width) === x && (0...@height) === y
  333. @field[y][x]
  334. end
  335.  
  336. def dump
  337. Marshal.dump(@field)
  338. end
  339.  
  340. def restore(port)
  341. @field.replace(Marshal.load(port))
  342. end
  343. end
  344.  
  345.  
  346.  
  347. class NumberLink::Cell
  348. include CellInteraction
  349. include CellSolver
  350.  
  351. attr_reader :pos, :field
  352. attr_accessor :marks, :connection, :disconnection
  353.  
  354. def initialize(position, marks, edge, disconnection, field, width, height)
  355. @pos = position
  356. @marks = marks
  357. @edge = edge
  358. @connection = []
  359. @disconnection = disconnection
  360. @fixed = false
  361. @field = field
  362. @width = width
  363. @height = height
  364. end
  365.  
  366. def to_s
  367. return ' ' if @marks.empty?
  368. edge_str = ((@connection == [:left] ? '─%s' : '%2s') % @marks)[-2, 2]
  369. return edge_str if @edge
  370. case @connection.sort
  371. when [:left, :right] then '──'
  372. when [:left, :up] then '─┘'
  373. when [:down, :left] then '─┐'
  374. when [:right, :up] then ' └'
  375. when [:down, :right] then ' ┌'
  376. when [:down, :up] then ' │'
  377. when [:left] then '- '
  378. when [:right] then ' -'
  379. when [:up] then ' \''
  380. when [:down] then ' ,'
  381. else ' ?'
  382. end
  383. end
  384.  
  385. def fix
  386. return if fixed?
  387. @fixed = true
  388. freeze
  389. end
  390.  
  391. def fixed?
  392. @fixed
  393. end
  394.  
  395. def fixable?
  396. !fixed? && identified? && directed?
  397. end
  398.  
  399. def directed?
  400. @connection.size + @disconnection.size == 4
  401. end
  402.  
  403. def edge?
  404. @edge
  405. end
  406.  
  407. def pos_x
  408. @pos[0]
  409. end
  410.  
  411. def pos_y
  412. @pos[1]
  413. end
  414.  
  415. def identified?
  416. @marks.size == 1
  417. end
  418.  
  419. def straight?
  420. @connection.sort!
  421. @connection == [:down, :up] || @connection == [:left, :right]
  422. end
  423.  
  424. def bended?
  425. @connection.size == 2 && !straight?
  426. end
  427.  
  428. def rest_directions
  429. [:left, :up, :right, :down] - (@connection + @disconnection)
  430. end
  431. end
  432.  
  433.  
  434.  
  435. $stdin.readlines($/ * 2).each do |str|
  436. begin
  437. puts NumberLink.new(str.chomp.gsub(/\*|\w/, ' \\& ')).reductio_ad_absurdum
  438. rescue NumberLink::Cell::CellTypeError
  439. puts 'No Solution'
  440. rescue NumberLink::TypeError => ex
  441. puts ex
  442. end
  443. puts '-' * 48
  444. end
  445.  
Success #stdin #stdout 3.98s 8692KB
stdin
***rg
**bg*
r****
ob*yo
****y

1213******
*****5****
**********
******6***
**********
*2********
**********
****47****
*6*****754
*********3

12*******1
*3********
*45*******
******5***
***6******
***2**6***
********4*
*******3**
**********
**********

********45
*2*****8**
1****6*3**
**********
**********
****7**6**
***5**38**
********7*
***4******
*2*******1

*******7*2
*******8**
**5***6***
*****4**1*
*****5****
13*46*****
******3***
**8****7**
*****2****
**********

*********1
**23****4*
**56******
**********
****1*****
**********
**********
***5**76**
**27**43**
**********

*******c*********d
*a*****d*********e
*b********ha******
***g**********on**
***h********n*****
*******i****o***e*
*******j**l*****f*
****k*****m**m****
*bi*l********k*jg*
*******cf*********

a*****************
**b***c*****d***e*
****f***gh****i***
*c***j******j*****
**************ka**
**hg**************
*****i******e***l*
***f****mk***n****
*m***n*****l***d**
*****************b

1*************3***
**2********4****9*
******7******6****
**3*************8*
************7*****
********6*********
************5*****
**4***********9***
****5*8********2**
*****************1

************************
*kl************t********
*****mn*****************
**l*****j***h**u****v***
******k***a**e**v*******
*m*******b****b***f*****
ji*****ic***************
***************gu*****nt
*****o***d****f*******r*
*******q**a**e***h******
***p****p**c***g*****s**
*****************qd*****
********o************sr*
************************

*****a*****************n
*********************p**
**k***b*****h*********f*
**********f*************
*******c*********d***i**
*****b********p*********
***g****d***o***********
***********m***o****c***
*********e********n*****
**a***i*********l*******
**************j*********
*e**********l****k***h**
**g*********************
j*****************m*****
stdout
 ┌─────r g
 │ ┌─b g─┘
 r │ ┌───┐
 o b │ y o
 └───┘ └─y
------------------------------------------------
 1 2 1 3 ┌─────────┐
 │ │ │ │ │ 5─────┐ │
 │ │ │ │ │ ┌───┐ │ │
 │ │ │ │ │ │ 6 │ │ │
 │ │ │ │ │ │ │ │ │ │
 │ 2 │ │ │ │ │ │ │ │
 └───┘ │ │ │ │ │ │ │
 ┌─────┘ 4 7 │ │ │ │
 │ 6─────────┘ 7 5 4
 └─────────────────3
------------------------------------------------
 1 2─────────────┐ 1
 │ 3───────────┐ │ │
 │ 4 5───────┐ │ │ │
 │ │ ┌─────┐ 5 │ │ │
 │ │ │ 6─┐ └───┘ │ │
 │ │ │ 2 └───6 ┌─┘ │
 │ │ │ └───────┘ 4 │
 │ │ └─────────3 │ │
 │ └─────────────┘ │
 └─────────────────┘
------------------------------------------------
 ┌───┐ ┌─────────4 5
 │ 2 │ │ ┌───┐ 8─┐ │
 1 │ │ │ │ 6 └─3 │ │
 ┌─┘ │ │ │ └───┐ │ │
 │ ┌─┘ │ └───┐ │ │ │
 │ │ ┌─┘ 7─┐ │ 6 │ │
 │ │ │ 5─┐ │ 3 8─┘ │
 │ │ └─┐ │ └─────7 │
 │ └─┐ 4 └─────────┘
 └─2 └─────────────1
------------------------------------------------
 ┌───────────┐ 7─┐ 2
 │ ┌───────┐ └─8 │ │
 │ │ 5───┐ │ 6 ┌─┘ │
 │ └───┐ │ 4 │ │ 1 │
 └───┐ │ └─5 │ │ │ │
 1 3 │ 4 6───┘ │ │ │
 │ │ │ ┌─────3 │ │ │
 │ │ 8 │ ┌───┐ 7 │ │
 │ └───┘ │ 2 └───┘ │
 └───────┘ └───────┘
------------------------------------------------
 ┌─────────────────1
 │ ┌─2 3───────┐ 4─┐
 │ │ 5 6─────┐ └─┐ │
 │ │ │ ┌───┐ └─┐ │ │
 │ │ │ │ 1 └─┐ │ │ │
 │ │ │ │ └─┐ │ │ │ │
 │ │ │ └─┐ │ │ │ │ │
 │ │ └─5 │ │ 7 6 │ │
 │ └─2 7─┘ │ 4 3─┘ │
 └─────────┘ └─────┘
------------------------------------------------
 ┌─────────────c ┌─────────────────d
 │ a─────────┐ d─┘ ┌───┐ ┌───────┐ e
 │ b ┌─────┐ └─────┘ h a │ ┌───┐ │ │
 │ │ │ g─┐ └─────────┘ ┌─┘ │ o n │ │
 │ │ └─h └─────────────┘ n─┘ │ ┌─┘ │
 │ │ ┌─────────i ┌─────┐ o───┘ │ e─┘
 │ │ │ ┌─────┐ j─┘ ┌─l └─────┐ │ f─┐
 │ │ │ │ k─┐ └─────┘ m─────m │ └─┐ │
 │ b i └─l └───────────────k └─j g │
 └─────────────c f─────────────────┘
------------------------------------------------
 a─────────────────────┐ ┌─────────┐
 ┌───b ┌─────c ┌─────┐ │ d ┌───┐ e │
 │ ┌───┘ f ┌───┘ g h │ └───┘ i │ │ │
 │ c ┌───┘ j ┌───┘ │ └───j ┌─┘ │ │ │
 │ ┌─┘ ┌─────┘ ┌───┘ ┌─────┘ k a │ │
 │ │ h g ┌─────┘ ┌───┘ ┌─────┘ ┌─┘ │
 │ │ └───┘ i─────┘ ┌───┘ e─────┘ l │
 │ └───f ┌───────m k ┌─────n ┌───┘ │
 │ m─────┘ n─────────┘ l─────┘ d───┘
 └─────────────────────────────────b
------------------------------------------------
 1─────┐ ┌───────────────────3 ┌───┐
 ┌───2 │ │ ┌───────────4 ┌─────┘ 9 │
 │ ┌───┘ │ │ 7─────────┐ │ 6─┐ ┌─┘ │
 │ │ 3───┘ │ ┌───────┐ │ └─┐ │ │ 8 │
 │ │ ┌─────┘ │ ┌───┐ │ └─7 │ │ │ │ │
 │ │ │ ┌─────┘ │ 6 │ └─────┘ │ │ │ │
 │ │ │ │ ┌─────┘ │ └─────5 ┌─┘ │ │ │
 │ │ 4 │ │ ┌───┐ └─────────┘ 9─┘ │ │
 │ └───┘ 5 │ 8 └───────────────2 │ │
 └─────────┘ └───────────────────┘ 1
------------------------------------------------
 ┌───────────────┐ ┌───────────────────────────┐
 │ k l ┌───────┐ │ │ ┌───────┐ t─────────────┐ │
 │ │ │ │ ┌─m n │ │ │ │ ┌───┐ └─────────────┐ │ │
 │ │ l │ │ ┌─┘ │ j │ │ │ h └─┐ u ┌───────v │ │ │
 │ └───┘ │ │ k─┘ ┌─┘ a │ │ e │ │ v ┌───┐ ┌─┘ │ │
 │ m─────┘ └─────┘ b───┘ │ │ b └───┘ f │ │ ┌─┘ │
 j i───────────i c─────┐ │ │ ┌───────┘ │ │ │ ┌─┘
 ┌───────────────────┐ │ │ │ │ g u─────┘ │ │ n t
 │ ┌───────o ┌─────d │ │ │ │ f └─────┐ ┌─┘ │ r │
 │ │ ┌───────┘ q───┐ a │ │ e ┌─────h │ │ ┌─┘ │ │
 │ │ │ p─────────p └─┐ c └───┘ g─────┘ │ │ s │ │
 │ │ └─────────────┐ └─────────────q d │ │ │ │ │
 │ └─────────────o └─────────────────┘ │ │ s r │
 └─────────────────────────────────────┘ └─────┘
------------------------------------------------
 ┌─────────a ┌───────────────────────────────┐ n
 │ ┌───┐ ┌───┘ ┌─────────────────────────┐ p │ │
 │ │ k │ │ ┌─b │ ┌─────┐ h─────────────┐ │ │ f │
 │ │ │ │ │ │ ┌─┘ │ ┌─f └─────────────┐ │ │ └─┐ │
 │ │ │ │ │ │ │ c─┘ │ ┌─────────────d │ │ └─i │ │
 │ │ │ │ │ b │ ┌───┘ │ ┌───┐ p─────┐ │ └───┐ │ │
 │ │ │ g └─┐ │ │ d───┘ │ o └─────┐ │ └───┐ │ │ │
 │ │ └───┐ │ │ │ ┌───┐ m └─────o │ └───┐ c │ │ │
 │ └───┐ │ │ │ │ │ e └─────────┐ └─┐ n └─┐ │ │ │
 └───a │ │ │ i │ │ │ ┌───────┐ │ l │ └─┐ │ │ │ │
 ┌─────┘ │ └───┘ │ │ │ ┌───┐ j │ │ └─┐ │ │ │ │ │
 │ e───┐ └───────┘ │ │ │ l └───┘ │ k │ │ │ h │ │
 └───g └───────────┘ │ │ └───────┘ │ │ │ └───┘ │
 j───────────────────┘ └───────────┘ m └───────┘
------------------------------------------------