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 +7 -0
- data/lib/dlx/header.rb +39 -0
- data/lib/dlx/node.rb +64 -0
- data/lib/dlx/sparse_matrix.rb +128 -0
- metadata +89 -0
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: []
|