dlx 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 71a827908e5e0e0f08c2dca9ea7eb683a10a4336
4
+ data.tar.gz: f91dcd989098989f8b9a9289d27b5161212d4256
5
+ SHA512:
6
+ metadata.gz: 0b815cb9731854dd12c7c566bb00f3769391e285197c58fe5d334a34b9228604030176e74ba70e73500c3a866c7370c3a224a738304e7d0801e9357e9609dfae
7
+ data.tar.gz: 05d6cd7fa985ce6d79924bd4b50aa191cb2203415026e988d249523deb7a96b77240fb0e8a6845a2ba0b2ebb9e6813b9870dc59c8baaec558c29b4b84c937a60
data/lib/dlx/header.rb ADDED
@@ -0,0 +1,39 @@
1
+ require_relative 'node'
2
+
3
+ module Dlx
4
+ class Header < Node
5
+
6
+ attr_accessor :total
7
+
8
+ def initialize(row, col, *args)
9
+ super(row, col, self, *args)
10
+ @total = 0
11
+ end
12
+
13
+ # Adds a node to the bottom of Header's column.
14
+ def add(node)
15
+ @total += 1
16
+ u = self.up
17
+
18
+ up.down = node
19
+ node.up = up
20
+
21
+ node.down = self
22
+ self.up = node
23
+
24
+ self
25
+ end
26
+
27
+ def remove(type)
28
+ simple_remove(type)
29
+ end
30
+
31
+ def restore(type)
32
+ simple_restore(type)
33
+ end
34
+
35
+ def to_s
36
+ "Header[#{row}][#{col}]"
37
+ end
38
+ end
39
+ end
data/lib/dlx/node.rb ADDED
@@ -0,0 +1,64 @@
1
+ module Dlx
2
+ class Node
3
+ attr_reader :row, :col, :header
4
+ attr_accessor :up, :right, :down, :left
5
+
6
+ def initialize(row, col, header, up = self, right = self, down = self, left = self)
7
+ @row, @col = row, col
8
+ @header = header
9
+ @up, @right, @down, @left = up, right, down, left
10
+ end
11
+
12
+ def link(nodes)
13
+ nodes.each_pair { |dir, node| send("#{dir}=", node) }
14
+ end
15
+
16
+ def remove(type)
17
+ header.total -= 1 if type == :column
18
+ simple_remove(type)
19
+ end
20
+
21
+ def restore(type)
22
+ header.total += 1 if type == :column
23
+ simple_restore(type)
24
+ end
25
+
26
+ # Traverses doubly linked list in the specified direction.
27
+ # Begins with yieding adjacent node, breaks _before_ yielding self.
28
+ def each_in(direction)
29
+ return enum_for(:each_in, direction) unless block_given?
30
+ node = self
31
+ until (node = node.send(direction)) == self do
32
+ yield node
33
+ end
34
+ end
35
+
36
+ def to_s
37
+ "Node[#{row}][#{col}]"
38
+ end
39
+
40
+ private
41
+
42
+ def simple_remove(type)
43
+ case type
44
+ when :row
45
+ left.right = right
46
+ right.left = left
47
+ when :column
48
+ up.down = down
49
+ down.up = up
50
+ end
51
+ end
52
+
53
+ def simple_restore(type)
54
+ case type
55
+ when :row
56
+ left.right = self
57
+ right.left = self
58
+ when :column
59
+ up.down = self
60
+ down.up = self
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,128 @@
1
+ require_relative 'header'
2
+ require_relative 'node'
3
+
4
+ module Dlx
5
+ class SparseMatrix
6
+ attr_reader :root, :width, :height, :headers
7
+
8
+ def initialize
9
+ @root = Dlx::Header.new(0, -1)
10
+ @string_rows = Array.new
11
+ @width = @height = 0
12
+ @headers = Array.new
13
+ end
14
+
15
+ def <<(string)
16
+ add(string)
17
+ end
18
+
19
+ def add(string)
20
+ raise ArgumentError, "Empty string" if string.empty?
21
+ if @width == 0
22
+ @width = string.length
23
+ create_headers
24
+ elsif string.length != @width
25
+ raise ArgumentError, "Width is: #{string.length}, expected: #{@width}"
26
+ end
27
+
28
+ row = []
29
+ string.scan(/./).zip(@headers).each do |l, header|
30
+ if l == "1"
31
+ node = Dlx::Node.new(@height, header.col, header)
32
+ row << node
33
+ header.add(node)
34
+ end
35
+ end
36
+
37
+ link_row(row)
38
+
39
+ @string_rows << string
40
+ @height += 1
41
+ self
42
+ end
43
+
44
+ def create_headers
45
+ @width.times do |col|
46
+ @headers << Dlx::Header.new(0, col)
47
+ end
48
+ link_row(@headers)
49
+ @root.right = @headers.first
50
+ @root.right.left = @root
51
+ @root.left = @headers.last
52
+ @root.left.right = @root
53
+ end
54
+
55
+ def link_row(row)
56
+ row_length = row.length
57
+ row.each_with_index do |node, i|
58
+ node.link({ right: row[(i+1) % row_length],
59
+ left: row[(i-1) % row_length]})
60
+ end
61
+ end
62
+
63
+ def rows
64
+ @string_rows
65
+ end
66
+
67
+ def solve(&block)
68
+ if block
69
+ search(0, [], &block)
70
+ else
71
+ solutions = Array.new
72
+ search(0, []) { |solution| solutions << solution }
73
+ solutions
74
+ end
75
+ end
76
+
77
+ # Returns header with least total.
78
+ def next_header
79
+ min_header = nil
80
+ @root.each_in(:right) do |header|
81
+ if !min_header || header.total < min_header.total
82
+ min_header = header
83
+ end
84
+ end
85
+ min_header
86
+ end
87
+
88
+ # Removes given node's column from matrix. Removes each node in same row as
89
+ # given node from their columns
90
+ def cover(node)
91
+ node.header.remove(:row)
92
+ node.header.each_in(:down) do |row|
93
+ row.each_in(:right) { |n| n.remove(:column) }
94
+ end
95
+ end
96
+
97
+ # Reverses cover.
98
+ def uncover(node)
99
+ node.header.each_in(:up) do |row|
100
+ row.each_in(:left) { |n| n.restore(:column) }
101
+ end
102
+ node.header.restore(:row)
103
+ end
104
+
105
+ private
106
+
107
+ def search(n, solution, &b)
108
+ if next_header.nil?
109
+ b.call(solution.dup)
110
+ else
111
+ header = next_header
112
+ cover(header)
113
+
114
+ header.each_in(:down) do |rowNode|
115
+ solution.push(@string_rows[rowNode.row])
116
+
117
+ rowNode.each_in(:right) { |rightNode| cover(rightNode) }
118
+
119
+ search(n + 1, solution, &b)
120
+ solution.pop
121
+
122
+ rowNode.each_in(:left) { |leftNode| uncover(leftNode) }
123
+ end
124
+ uncover(header)
125
+ end
126
+ end
127
+ end
128
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dlx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Sam Davis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Dancing Links - Finding all solutions to the exact cover problem
56
+ email:
57
+ - sam@samgd.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - lib/dlx/header.rb
63
+ - lib/dlx/node.rb
64
+ - lib/dlx/sparse_matrix.rb
65
+ homepage: https://github.com/samgd/dlx
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.4.5
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: Dancing Links
89
+ test_files: []