csp-resolver 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/.rubocop.yml +28 -0
- data/MIT-LICENSE +20 -0
- data/README.md +260 -0
- data/Rakefile +4 -0
- data/csp-resolver.gemspec +31 -0
- data/examples/event_scheduling.rb +48 -0
- data/examples/map_coloring.rb +62 -0
- data/examples/queen.rb +45 -0
- data/examples/sculpture.rb +65 -0
- data/lib/csp/algorithms/backtracking.rb +112 -0
- data/lib/csp/algorithms/filtering/no_filter.rb +23 -0
- data/lib/csp/algorithms/lookahead/ac3.rb +100 -0
- data/lib/csp/algorithms/lookahead/no_algorithm.rb +19 -0
- data/lib/csp/algorithms/ordering/no_order.rb +23 -0
- data/lib/csp/constraint.rb +27 -0
- data/lib/csp/constraints.rb +49 -0
- data/lib/csp/problem.rb +124 -0
- data/lib/csp/utils.rb +15 -0
- data/lib/csp/version.rb +5 -0
- data/lib/csp-resolver.rb +14 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a8b89688ec5b6907524a3b31a8159a7c78dbe61578ed77e47ddeddfb956777d4
|
4
|
+
data.tar.gz: 2791072bb632b2a19151ba5bd60c2ecbdb2923edfd727a0e3ccb6c1f30546ecb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0ec7e93632f200c4ffc71d702146acfc64e3395c8a77c987f0190dd64f6f2b377d858e3f8cbbb2341b70d8476ce86719a853359d043546eb0e7a8df374a73bf8
|
7
|
+
data.tar.gz: a0f7552bd93a53f387052eeba3b8a3cb411671d362e0bbb1524b41d176acd0d842d29d4e1be08e847d3c6b2e33dc5588dedebb1de2932018f57bb3a97b9d4a33
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.5
|
3
|
+
NewCops: enable
|
4
|
+
SuggestExtensions: false
|
5
|
+
Metrics/BlockLength:
|
6
|
+
Exclude:
|
7
|
+
- spec/**/*.rb
|
8
|
+
Metrics/ClassLength:
|
9
|
+
Max: 200
|
10
|
+
Metrics/MethodLength:
|
11
|
+
Max: 20
|
12
|
+
Layout/FirstArrayElementIndentation:
|
13
|
+
EnforcedStyle: consistent
|
14
|
+
Layout/FirstHashElementIndentation:
|
15
|
+
EnforcedStyle: consistent
|
16
|
+
Layout/HashAlignment:
|
17
|
+
EnforcedLastArgumentHashStyle: always_inspect
|
18
|
+
Layout/MultilineMethodCallIndentation:
|
19
|
+
EnforcedStyle: indented
|
20
|
+
Style/Documentation:
|
21
|
+
Enabled: false
|
22
|
+
Layout/EndAlignment:
|
23
|
+
EnforcedStyleAlignWith: variable
|
24
|
+
Style/ObjectThen:
|
25
|
+
EnforcedStyle: yield_self
|
26
|
+
Naming/FileName:
|
27
|
+
Exclude:
|
28
|
+
- 'lib/csp-resolver.rb'
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2024 Rebase, André Benjamim, Gustavo Alberto.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
# CSP Resolver
|
2
|
+
The `csp-resolver` gem is a powerful tool designed to solve [Constraint Satisfaction Problems](https://en.wikipedia.org/wiki/Constraint_satisfaction_problem) (CSPs), which are mathematical questions defined by strict constraints that must be met. This tool is suitable for a wide range of applications, from scheduling and planning to configuring complex systems.
|
3
|
+
|
4
|
+
## Getting Started
|
5
|
+
### Requirements
|
6
|
+
**Ruby** >= 2.5.8
|
7
|
+
|
8
|
+
### Installing
|
9
|
+
You can install using the following command:
|
10
|
+
```bash
|
11
|
+
gem install "csp-resolver"
|
12
|
+
```
|
13
|
+
|
14
|
+
If you prefer using Bundler, add the following line to your Gemfile:
|
15
|
+
```bash
|
16
|
+
gem "csp-resolver"
|
17
|
+
```
|
18
|
+
|
19
|
+
Then install it:
|
20
|
+
```bash
|
21
|
+
$ bundle install
|
22
|
+
```
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
### **Setup a problem to be solved**
|
27
|
+
To setup a problem we need to require the gem and initialize the CSP:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'csp-resolver'
|
31
|
+
|
32
|
+
problem = CSP::Problem.new
|
33
|
+
```
|
34
|
+
|
35
|
+
### **Adding variables and domains**
|
36
|
+
|
37
|
+
To add variables and domains you can use the **`add_variable`** method:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
variable = 'A'
|
41
|
+
domains = %w[red green blue]
|
42
|
+
|
43
|
+
problem.add_variable(variable, domains: domains)
|
44
|
+
```
|
45
|
+
|
46
|
+
If some variables share the same domains, you can use **`add_variables`** (plural) for easier setup:
|
47
|
+
```ruby
|
48
|
+
variables = %w[B C]
|
49
|
+
domains = %w[red green blue]
|
50
|
+
|
51
|
+
problem.add_variables(variables, domains: domains)
|
52
|
+
|
53
|
+
# is the same as
|
54
|
+
problem.add_variable('B', domains: domains)
|
55
|
+
problem.add_variable('C', domains: domains)
|
56
|
+
```
|
57
|
+
|
58
|
+
### **Adding constraints**
|
59
|
+
There are three ways of adding a constraint: **built-in methods**, **custom block**, or **custom constraint class**.
|
60
|
+
|
61
|
+
#### **Using built-in methods**
|
62
|
+
Setting a list of variables to be unique between them:
|
63
|
+
```ruby
|
64
|
+
# A != B != C
|
65
|
+
problem.unique(%w[A B])
|
66
|
+
problem.unique(%w[A C])
|
67
|
+
problem.unique(%w[C B])
|
68
|
+
|
69
|
+
# same as
|
70
|
+
problem.unique(%w[A B C])
|
71
|
+
```
|
72
|
+
|
73
|
+
Setting all variable assignments to be different:
|
74
|
+
```ruby
|
75
|
+
# A != B != C
|
76
|
+
# It will consider all variables of CSP automatically
|
77
|
+
problem.all_different
|
78
|
+
```
|
79
|
+
|
80
|
+
#### **Using a custom block**
|
81
|
+
You can use the **`add_constraint`** method passing `variables` and a block to create custom validations:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
# Set B != C and B != A
|
85
|
+
problem.add_constraint(variables: %w[B C A]) { |b, c, a| b != c && b != a }
|
86
|
+
```
|
87
|
+
|
88
|
+
The block parameters should correspond to the order of the variables provided.
|
89
|
+
|
90
|
+
#### **Using a custom constraint class**
|
91
|
+
To create a custom constraint class it'll need to answer if an assignment satisfies a condition for a group of variables.
|
92
|
+
|
93
|
+
The easiest way to do this is inheriting from **`CSP::Constraint`**:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
class MyCustomConstraint < CSP::Constraint
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
Now the **`CustomConstraint`** can receive a list of variables which we will use to check if their assigned values conform to the constraint's rule.
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
variables = %w[A B C]
|
104
|
+
|
105
|
+
constraint = MyCustomConstraint.new(variables)
|
106
|
+
|
107
|
+
# It can answer the arity for constraint
|
108
|
+
constraint.unary? # => false
|
109
|
+
constraint.binary? # => false
|
110
|
+
constraint.arity # => 3
|
111
|
+
```
|
112
|
+
|
113
|
+
##### **Implementing the constraint rule**
|
114
|
+
To determinate if the solution satisfies or not a constraint we need to implement the **`satisfies?`** method. This method receives a hash containing the current variables assignments.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
# Variables can't have the color purple
|
118
|
+
|
119
|
+
class MyCustomConstraint < CSP::Constraint
|
120
|
+
def satisfies?(assignment = {})
|
121
|
+
# While not all variables for this constraint are assigned,
|
122
|
+
# consider that it doesn't violates the constraint.
|
123
|
+
return true if variables.all? { |variable| assignment[variable] }
|
124
|
+
|
125
|
+
variables.all? { |variable| assignment[variable] != 'purple' }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
##### **Adding the constraint to CSP**
|
131
|
+
To add the constraint we must instantiate it and pass the object to **`add_constraint`**:
|
132
|
+
```ruby
|
133
|
+
problem = CSP::Problem.new
|
134
|
+
problem.add_variables(%w[A B C], domains: %w[purple red green blue])
|
135
|
+
|
136
|
+
# B can't have the color purple
|
137
|
+
constraint = MyCustomConstraint.new(%w[B])
|
138
|
+
|
139
|
+
# Add the B != purple constraint
|
140
|
+
problem.add_constraint(constraint)
|
141
|
+
```
|
142
|
+
|
143
|
+
##### **The constructor**
|
144
|
+
|
145
|
+
The default constructor expects to receive an array of variables to apply the constraint.
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
class CSP::Constraint
|
149
|
+
def initialize(variables)
|
150
|
+
@variables = variables
|
151
|
+
end
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
But if you need to add other properties besides the variables, you can override the constructor:
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
# Instead of only purple, now we can choose which color to exclude.
|
159
|
+
class MyCustomConstraint < CSP::Constraint
|
160
|
+
def initialize(letters:, color:)
|
161
|
+
# set letters as the variables
|
162
|
+
super(letters)
|
163
|
+
|
164
|
+
@letters = letters
|
165
|
+
@color = color
|
166
|
+
end
|
167
|
+
|
168
|
+
def satisfies?(assignment = {})
|
169
|
+
# since letters is the same as variables, we can usem them interchangeably here.
|
170
|
+
return true if @letters.all? { |letter| assignment[letter].present? }
|
171
|
+
|
172
|
+
# we compare with the color set
|
173
|
+
@letters.all? { |letter| assignment[letter] != @color }
|
174
|
+
end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
And now we can use as we see fit:
|
179
|
+
```ruby
|
180
|
+
problem = CSP::Problem.new
|
181
|
+
problem.add_variables(%w[A B C], domains: %w[purple red green blue])
|
182
|
+
|
183
|
+
a_cant_be_green = MyCustomConstraint.new(letters: %w[A], color: 'green')
|
184
|
+
b_cant_be_purple = MyCustomConstraint.new(letters: %w[B], color: 'purple')
|
185
|
+
c_cant_be_blue = MyCustomConstraint.new(letters: %w[C], color: 'blue')
|
186
|
+
|
187
|
+
problem.add_constraint(a_cant_be_green)
|
188
|
+
problem.add_constraint(b_cant_be_purple)
|
189
|
+
problem.add_constraint(c_cant_be_blue)
|
190
|
+
```
|
191
|
+
|
192
|
+
##### **TL;DR**
|
193
|
+
* Inherit from `CSP::Constraint`
|
194
|
+
* Implement a `satisfies?(assignment = {})` that returns a boolean
|
195
|
+
* Override the initializer if needed, but pass to `super` the constraint's variables
|
196
|
+
|
197
|
+
### Solving the problem
|
198
|
+
After setting the problem we can search for the solution by calling `solve`:
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
problem.solve
|
202
|
+
# => { 'A' => 'green', 'B' => 'red', 'C' => 'purple' }
|
203
|
+
```
|
204
|
+
|
205
|
+
### Full Example:
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
# Given the letters A-C, pick a color between red, blue, green, and purple for them.
|
209
|
+
# Consider the following rules:
|
210
|
+
# * Each letters has a unique color
|
211
|
+
# * A can't have the color purple
|
212
|
+
# * A and C can't have the color red
|
213
|
+
# * B can't have the color purple nor green
|
214
|
+
|
215
|
+
# Create a constraint class
|
216
|
+
class MyCustomConstraint < CSP::Constraint
|
217
|
+
def initialize(letters:, color:)
|
218
|
+
super(letters)
|
219
|
+
@letters = letters
|
220
|
+
@color = color
|
221
|
+
end
|
222
|
+
|
223
|
+
def satisfies?(assignment = {})
|
224
|
+
@letters.all? { |letter| assignment[letter] != @color }
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Initialize the problem
|
229
|
+
problem = CSP::Problem.new
|
230
|
+
|
231
|
+
# Define the letters as variables and colors as domains
|
232
|
+
variables = %w[A B C]
|
233
|
+
domains = %w[purple red green blue]
|
234
|
+
|
235
|
+
# Create constraints using the custom class
|
236
|
+
a_cant_be_purple = MyCustomConstraint.new(letters: %w[A], color: 'purple')
|
237
|
+
a_and_c_cant_be_red = MyCustomConstraint.new(letters: %w[A C], color: 'red')
|
238
|
+
|
239
|
+
# Add variables and domains
|
240
|
+
problem.add_variables(variables, domains: domains)
|
241
|
+
|
242
|
+
# Set the unique color constraint
|
243
|
+
problem.all_different
|
244
|
+
|
245
|
+
# set A != purple constraint
|
246
|
+
problem.add_constraint(a_cant_be_purple)
|
247
|
+
|
248
|
+
# set A != purple && C != purple constraint
|
249
|
+
problem.add_constraint(a_and_c_cant_be_red)
|
250
|
+
|
251
|
+
# set B != purple && B != green constraint
|
252
|
+
problem.add_constraint(variables: %w[B]) { |b| b != 'purple' && b != 'green' }
|
253
|
+
|
254
|
+
# find the solution
|
255
|
+
problem.solve
|
256
|
+
# => { 'A' => 'green', 'B' => 'red', 'C' => 'purple' }
|
257
|
+
```
|
258
|
+
|
259
|
+
## License
|
260
|
+
This project is licensed under the [MIT License](MIT-LICENSE).
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/csp/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'csp-resolver'
|
7
|
+
spec.version = CSP::VERSION
|
8
|
+
spec.license = 'MIT'
|
9
|
+
spec.authors = ['André Benjamim', 'Gustavo Alberto']
|
10
|
+
spec.email = ['andre.benjamim@rebase.com.br', 'gustavo.costa@rebase.com.br']
|
11
|
+
|
12
|
+
spec.summary = 'A Ruby CSP Solver'
|
13
|
+
spec.description = 'This Ruby gem solves CSPs using custom constraints'
|
14
|
+
spec.homepage = 'https://github.com/Rebase-BR/csp-resolver'
|
15
|
+
spec.required_ruby_version = '>= 2.5.8'
|
16
|
+
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
spec.metadata['source_code_uri'] = 'https://github.com/Rebase-BR/csp-resolver'
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(__dir__) do
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
+
(File.expand_path(f) == __FILE__) ||
|
25
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
spec.bindir = 'exe'
|
29
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ['lib']
|
31
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/csp-resolver'
|
4
|
+
|
5
|
+
module CSP
|
6
|
+
module Examples
|
7
|
+
class EventScheduling
|
8
|
+
def call
|
9
|
+
number_of_events = 3
|
10
|
+
number_of_time_slots = 4
|
11
|
+
|
12
|
+
variables = number_of_events.times.to_a
|
13
|
+
domains = number_of_time_slots.times.to_a
|
14
|
+
|
15
|
+
csp = CSP::Problem.new
|
16
|
+
.add_variables(variables, domains: domains)
|
17
|
+
|
18
|
+
variables.combination(2).each do |events|
|
19
|
+
add_constraint(csp, *events)
|
20
|
+
end
|
21
|
+
|
22
|
+
solution = csp.solve
|
23
|
+
solution || 'No solution found'
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_constraint(csp, event1, event2)
|
27
|
+
csp.add_constraint(OnlyOneConstraint.new(event1, event2))
|
28
|
+
end
|
29
|
+
|
30
|
+
class OnlyOneConstraint < ::CSP::Constraint
|
31
|
+
attr_reader :event1, :event2
|
32
|
+
|
33
|
+
def initialize(event1, event2)
|
34
|
+
super([event1, event2])
|
35
|
+
|
36
|
+
@event1 = event1
|
37
|
+
@event2 = event2
|
38
|
+
end
|
39
|
+
|
40
|
+
def satisfies?(assignment)
|
41
|
+
return true if variables.any? { |variable| !assignment.key?(variable) }
|
42
|
+
|
43
|
+
assignment[event1] != assignment[event2]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/csp-resolver'
|
4
|
+
|
5
|
+
module CSP
|
6
|
+
module Examples
|
7
|
+
class MapColoring
|
8
|
+
def call # rubocop:disable Metrics/MethodLength
|
9
|
+
variables = [
|
10
|
+
'Western Australia',
|
11
|
+
'Northern Territory',
|
12
|
+
'South Australia',
|
13
|
+
'Queensland',
|
14
|
+
'New South Wales',
|
15
|
+
'Victoria',
|
16
|
+
'Tasmania'
|
17
|
+
]
|
18
|
+
|
19
|
+
domains = %w[red blue green]
|
20
|
+
|
21
|
+
csp = CSP::Problem.new
|
22
|
+
.add_variables(variables, domains: domains)
|
23
|
+
|
24
|
+
add_constraint(csp, 'Western Australia', 'Northern Territory')
|
25
|
+
add_constraint(csp, 'Western Australia', 'South Australia')
|
26
|
+
add_constraint(csp, 'South Australia', 'Northern Territory')
|
27
|
+
add_constraint(csp, 'Queensland', 'Northern Territory')
|
28
|
+
add_constraint(csp, 'Queensland', 'South Australia')
|
29
|
+
add_constraint(csp, 'Queensland', 'New South Wales')
|
30
|
+
add_constraint(csp, 'New South Wales', 'South Australia')
|
31
|
+
add_constraint(csp, 'Victoria', 'South Australia')
|
32
|
+
add_constraint(csp, 'Victoria', 'New South Wales')
|
33
|
+
add_constraint(csp, 'Victoria', 'Tasmania')
|
34
|
+
|
35
|
+
solution = csp.solve
|
36
|
+
solution || 'No solution found'
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_constraint(csp, place1, place2)
|
40
|
+
csp.add_constraint(MapColoringConstraint.new(place1, place2))
|
41
|
+
end
|
42
|
+
|
43
|
+
class MapColoringConstraint < ::CSP::Constraint
|
44
|
+
attr_reader :place1, :place2
|
45
|
+
|
46
|
+
def initialize(place1, place2)
|
47
|
+
super([place1, place2])
|
48
|
+
|
49
|
+
@place1 = place1
|
50
|
+
@place2 = place2
|
51
|
+
end
|
52
|
+
|
53
|
+
def satisfies?(assignment)
|
54
|
+
# If any of them is not assigned then there's no conflict
|
55
|
+
return true if variables.any? { |variable| !assignment.key?(variable) }
|
56
|
+
|
57
|
+
assignment[place1] != assignment[place2]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/examples/queen.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/csp-resolver'
|
4
|
+
|
5
|
+
module CSP
|
6
|
+
module Examples
|
7
|
+
class Queen
|
8
|
+
def call(queens_number = 8)
|
9
|
+
variables = queens_number.times.to_a
|
10
|
+
|
11
|
+
csp = CSP::Problem.new
|
12
|
+
.add_variables(variables, domains: variables)
|
13
|
+
.add_constraint(QueensConstraint.new(variables))
|
14
|
+
solution = csp.solve
|
15
|
+
|
16
|
+
solution || 'No solution found'
|
17
|
+
end
|
18
|
+
|
19
|
+
class QueensConstraint < ::CSP::Constraint
|
20
|
+
attr_reader :columns
|
21
|
+
|
22
|
+
def initialize(columns)
|
23
|
+
super(columns)
|
24
|
+
|
25
|
+
@columns = columns
|
26
|
+
end
|
27
|
+
|
28
|
+
def satisfies?(assignment)
|
29
|
+
assignment.each do |(queen_col1, queen_row1)|
|
30
|
+
(queen_col1 + 1..columns.size).each do |queen_col2|
|
31
|
+
next unless assignment.key?(queen_col2)
|
32
|
+
|
33
|
+
queen_row2 = assignment[queen_col2]
|
34
|
+
|
35
|
+
return false if queen_row1 == queen_row2
|
36
|
+
return false if (queen_row1 - queen_row2).abs == (queen_col1 - queen_col2).abs
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/csp-resolver'
|
4
|
+
|
5
|
+
module CSP
|
6
|
+
module Examples
|
7
|
+
# Three sculptures (A, B, C) are to be exhibited in rooms 1,2 of an art gallery
|
8
|
+
#
|
9
|
+
# The exhibition must satisfy the following conditions:
|
10
|
+
# 1. Sculptures A and B cannot be in the same room
|
11
|
+
# 2. Sculptures B and C must be in the same room
|
12
|
+
# 3. Room 2 can only hold one sculpture
|
13
|
+
class Sculpture
|
14
|
+
def call
|
15
|
+
variables = %w[A B C]
|
16
|
+
|
17
|
+
csp = CSP::Problem.new
|
18
|
+
.add_variable('A', domains: [1, 2])
|
19
|
+
.add_variable('B', domains: [1, 2])
|
20
|
+
.add_variable('C', domains: [1, 2])
|
21
|
+
.unique(%w[A B])
|
22
|
+
.add_constraint(variables: %w[B C]) { |b, c| b == c }
|
23
|
+
.add_constraint(RoomLimitToOneConstraint.new(room: 2, variables: variables))
|
24
|
+
solution = csp.solve
|
25
|
+
|
26
|
+
solution || 'No solution found'
|
27
|
+
end
|
28
|
+
|
29
|
+
class CannotBeInSameRoomConstraint < ::CSP::Constraint
|
30
|
+
def satisfies?(assignment)
|
31
|
+
values = assignment.values_at(*variables)
|
32
|
+
|
33
|
+
return true if values.any?(&:nil?)
|
34
|
+
|
35
|
+
values == values.uniq
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class MustBeInSameRoomConstraint < ::CSP::Constraint
|
40
|
+
def satisfies?(assignment)
|
41
|
+
values = assignment.values_at(*variables)
|
42
|
+
|
43
|
+
return true if values.any?(&:nil?)
|
44
|
+
|
45
|
+
values.uniq.size == 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class RoomLimitToOneConstraint < ::CSP::Constraint
|
50
|
+
attr_reader :room
|
51
|
+
|
52
|
+
def initialize(room:, variables:)
|
53
|
+
super(variables)
|
54
|
+
@room = room
|
55
|
+
end
|
56
|
+
|
57
|
+
def satisfies?(assignment)
|
58
|
+
values = assignment.values_at(*variables)
|
59
|
+
|
60
|
+
values.count(room) <= 1
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module CSP
|
6
|
+
module Algorithms
|
7
|
+
class Backtracking
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
ORDERING_ALGORITHM = Ordering::NoOrder
|
11
|
+
FILTERING_ALGORITHM = Filtering::NoFilter
|
12
|
+
LOOKAHEAD_ALGORITHM = Lookahead::NoAlgorithm
|
13
|
+
|
14
|
+
attr_reader :problem, :solutions, :max_solutions,
|
15
|
+
:ordering_algorithm, :filtering_algorithm, :lookahead_algorithm
|
16
|
+
|
17
|
+
def_delegators :problem, :variables, :constraints
|
18
|
+
|
19
|
+
def initialize(
|
20
|
+
problem:,
|
21
|
+
ordering_algorithm: nil,
|
22
|
+
filtering_algorithm: nil,
|
23
|
+
lookahead_algorithm: nil,
|
24
|
+
max_solutions: 1
|
25
|
+
)
|
26
|
+
@problem = problem
|
27
|
+
@ordering_algorithm = ordering_algorithm || ORDERING_ALGORITHM.new(problem)
|
28
|
+
@filtering_algorithm = filtering_algorithm || FILTERING_ALGORITHM.new(problem)
|
29
|
+
@lookahead_algorithm = lookahead_algorithm || LOOKAHEAD_ALGORITHM.new(problem)
|
30
|
+
@max_solutions = max_solutions
|
31
|
+
@solutions = []
|
32
|
+
end
|
33
|
+
|
34
|
+
def backtracking(assignment = {})
|
35
|
+
backtracking_recursion(assignment, problem_domains)
|
36
|
+
end
|
37
|
+
|
38
|
+
def consistent?(variable, assignment)
|
39
|
+
constraints[variable].all? do |constraint|
|
40
|
+
constraint.satisfies?(assignment)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def problem_domains
|
47
|
+
problem.domains
|
48
|
+
end
|
49
|
+
|
50
|
+
def backtracking_recursion(assignment, domains)
|
51
|
+
return solutions if max_solutions?
|
52
|
+
return add_solution(assignment) if complete?(assignment)
|
53
|
+
|
54
|
+
unassigned = next_unassigned_variable(assignment)
|
55
|
+
|
56
|
+
domains_for(unassigned, assignment, domains).each do |value|
|
57
|
+
local_assignment = assignment.clone
|
58
|
+
local_assignment[unassigned] = value
|
59
|
+
|
60
|
+
next unless consistent?(unassigned, local_assignment)
|
61
|
+
|
62
|
+
new_domains = lookahead(local_assignment, domains)
|
63
|
+
|
64
|
+
next unless new_domains
|
65
|
+
|
66
|
+
backtracking_recursion(local_assignment, new_domains)
|
67
|
+
|
68
|
+
return solutions if max_solutions?
|
69
|
+
end
|
70
|
+
|
71
|
+
[]
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_solution(assignment)
|
75
|
+
solutions << assignment
|
76
|
+
end
|
77
|
+
|
78
|
+
def max_solutions?
|
79
|
+
solutions.size >= max_solutions
|
80
|
+
end
|
81
|
+
|
82
|
+
def complete?(assignment)
|
83
|
+
assignment.size == variables.size
|
84
|
+
end
|
85
|
+
|
86
|
+
def next_unassigned_variable(assignment)
|
87
|
+
unassigned_variables(assignment).first
|
88
|
+
end
|
89
|
+
|
90
|
+
def unassigned_variables(assignment)
|
91
|
+
variables
|
92
|
+
.reject { |variable| assignment.key?(variable) }
|
93
|
+
.yield_self { |v| ordering_algorithm.call(v) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def domains_for(unassigned, assignment, domains)
|
97
|
+
filtering_algorithm.call(
|
98
|
+
values: domains[unassigned],
|
99
|
+
assignment_values: assignment.values.flatten
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
def lookahead(assignment, domains)
|
104
|
+
lookahead_algorithm.call(
|
105
|
+
variables: variables,
|
106
|
+
assignment: assignment,
|
107
|
+
domains: domains
|
108
|
+
)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSP
|
4
|
+
module Algorithms
|
5
|
+
module Filtering
|
6
|
+
class NoFilter
|
7
|
+
attr_reader :problem
|
8
|
+
|
9
|
+
def self.for(problem:, dependency: nil) # rubocop:disable Lint/UnusedMethodArgument
|
10
|
+
new(problem)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(problem)
|
14
|
+
@problem = problem
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(values:, assignment_values: []) # rubocop:disable Lint/UnusedMethodArgument
|
18
|
+
values
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSP
|
4
|
+
module Algorithms
|
5
|
+
module Lookahead
|
6
|
+
class Ac3
|
7
|
+
attr_reader :problem
|
8
|
+
|
9
|
+
def initialize(problem)
|
10
|
+
@problem = problem
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(variables:, assignment:, domains:)
|
14
|
+
new_domains = variables.each_with_object({}) do |variable, domains_hash|
|
15
|
+
variable_domains = Array(assignment[variable] || domains[variable])
|
16
|
+
|
17
|
+
domains_hash[variable] = unary_check(variable, variable_domains)
|
18
|
+
end
|
19
|
+
|
20
|
+
variable_arcs = arcs(variables)
|
21
|
+
|
22
|
+
arc_consistency(variable_arcs, new_domains)
|
23
|
+
end
|
24
|
+
|
25
|
+
def arc_consistency(arcs, domains)
|
26
|
+
queue = arcs.dup
|
27
|
+
|
28
|
+
until queue.empty?
|
29
|
+
arc, *queue = queue
|
30
|
+
x, y = arc.keys.first
|
31
|
+
constraint = arc.values.first
|
32
|
+
|
33
|
+
next unless arc_reduce(x, y, constraint, domains)
|
34
|
+
return nil if domains[x].empty?
|
35
|
+
|
36
|
+
new_arcs = find_arcs(x, y, arcs)
|
37
|
+
queue.push(*new_arcs)
|
38
|
+
end
|
39
|
+
|
40
|
+
domains
|
41
|
+
end
|
42
|
+
|
43
|
+
def arc_reduce(x, y, constraint, domains) # rubocop:disable Naming/MethodParameterName
|
44
|
+
changed = false
|
45
|
+
x_domains = domains[x]
|
46
|
+
y_domains = domains[y]
|
47
|
+
|
48
|
+
x_domains.each do |x_value|
|
49
|
+
consistent = y_domains.any? do |y_value|
|
50
|
+
sat = constraint.satisfies?({ x => x_value, y => y_value })
|
51
|
+
|
52
|
+
sat
|
53
|
+
end
|
54
|
+
|
55
|
+
next if consistent
|
56
|
+
|
57
|
+
x_domains -= [x_value]
|
58
|
+
changed = true
|
59
|
+
end
|
60
|
+
|
61
|
+
domains[x] = x_domains
|
62
|
+
|
63
|
+
changed
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns all (z, x) arcs where z != y
|
67
|
+
def find_arcs(x, y, arcs) # rubocop:disable Naming/MethodParameterName
|
68
|
+
arcs.select do |arc|
|
69
|
+
arc.any? do |(first, second), _constraint|
|
70
|
+
first != y && second == x
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Setup arcs between variables
|
76
|
+
def arcs(variables)
|
77
|
+
variables.each_with_object([]) do |variable, worklist|
|
78
|
+
constraints = problem.constraints[variable].select(&:binary?)
|
79
|
+
|
80
|
+
constraints.each do |constraint|
|
81
|
+
variables_ij = [variable] | constraint.variables # make current variable be the first
|
82
|
+
|
83
|
+
worklist << { variables_ij => constraint }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def unary_check(variable, variable_domains)
|
89
|
+
constraints = problem.constraints[variable].select(&:unary?)
|
90
|
+
|
91
|
+
variable_domains.select do |domain|
|
92
|
+
constraints.all? do |constraint|
|
93
|
+
constraint.satisfies?({ variable => domain })
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSP
|
4
|
+
module Algorithms
|
5
|
+
module Lookahead
|
6
|
+
class NoAlgorithm
|
7
|
+
attr_reader :problem
|
8
|
+
|
9
|
+
def initialize(problem)
|
10
|
+
@problem = problem
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(variables:, assignment:, domains:) # rubocop:disable Lint/UnusedMethodArgument
|
14
|
+
domains
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSP
|
4
|
+
module Algorithms
|
5
|
+
module Ordering
|
6
|
+
class NoOrder
|
7
|
+
attr_reader :problem
|
8
|
+
|
9
|
+
def self.for(problem:, dependency: nil) # rubocop:disable Lint/UnusedMethodArgument
|
10
|
+
new(problem)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(problem)
|
14
|
+
@problem = problem
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(variables)
|
18
|
+
variables
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSP
|
4
|
+
class Constraint
|
5
|
+
attr_reader :variables
|
6
|
+
|
7
|
+
def initialize(variables = [])
|
8
|
+
@variables = variables
|
9
|
+
end
|
10
|
+
|
11
|
+
def satisfies?(_assignment = {})
|
12
|
+
raise StandardError, 'Not Implemented. Should return a boolean'
|
13
|
+
end
|
14
|
+
|
15
|
+
def unary?
|
16
|
+
arity == 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def binary?
|
20
|
+
arity == 2
|
21
|
+
end
|
22
|
+
|
23
|
+
def arity
|
24
|
+
@arity ||= variables.size
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSP
|
4
|
+
module Constraints
|
5
|
+
class AllDifferentConstraint < CSP::Constraint
|
6
|
+
def satisfies?(assignment)
|
7
|
+
assignment.values == assignment.values.uniq
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class UniqueConstraint < CSP::Constraint
|
12
|
+
def satisfies?(assignment)
|
13
|
+
values = assignment.values_at(*variables)
|
14
|
+
|
15
|
+
return true if values.any?(&:nil?)
|
16
|
+
|
17
|
+
values == values.uniq
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class CustomConstraint < CSP::Constraint
|
22
|
+
attr_reader :block
|
23
|
+
|
24
|
+
def initialize(variables, block)
|
25
|
+
super(variables)
|
26
|
+
@block = block
|
27
|
+
end
|
28
|
+
|
29
|
+
def satisfies?(assignment)
|
30
|
+
values = assignment.values_at(*variables)
|
31
|
+
return true if values.any?(&:nil?)
|
32
|
+
|
33
|
+
block.call(*values)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def all_different
|
38
|
+
add_constraint(AllDifferentConstraint.new(variables))
|
39
|
+
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def unique(variables)
|
44
|
+
add_constraint(UniqueConstraint.new(variables))
|
45
|
+
|
46
|
+
self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/csp/problem.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSP
|
4
|
+
# TODO: implement dependent factor with weight
|
5
|
+
# TODO: implement lookahead, arc-consistency, ac3
|
6
|
+
class Problem
|
7
|
+
include CSP::Constraints
|
8
|
+
|
9
|
+
attr_reader :variables, :domains, :constraints, :max_solutions,
|
10
|
+
:ordering_algorithm, :filtering_algorithm, :lookahead_algorithm
|
11
|
+
|
12
|
+
InvalidConstraintVariable = Class.new(StandardError)
|
13
|
+
VariableShouldNotBeEmpty = Class.new(StandardError)
|
14
|
+
DomainsShouldNotBeEmpty = Class.new(StandardError)
|
15
|
+
VariableAlreadySeted = Class.new(StandardError)
|
16
|
+
|
17
|
+
def initialize(max_solutions: 1)
|
18
|
+
@variables = []
|
19
|
+
@domains = {}
|
20
|
+
@constraints = {}
|
21
|
+
@max_solutions = max_solutions
|
22
|
+
end
|
23
|
+
|
24
|
+
def solve(assignment = {})
|
25
|
+
Utils::Array.wrap(search_solution(assignment))
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_variable(variable, domains:)
|
29
|
+
if (variable.respond_to?(:empty?) && variable.empty?) || variable.nil?
|
30
|
+
raise VariableShouldNotBeEmpty, 'Variable was empty in the function parameter'
|
31
|
+
end
|
32
|
+
raise DomainsShouldNotBeEmpty, 'Domains was empty in the function parameter' if domains.empty?
|
33
|
+
raise VariableAlreadySeted, "Variable #{variable} has already been seted" if variables.include?(variable)
|
34
|
+
|
35
|
+
variables << variable
|
36
|
+
@domains[variable] = domains
|
37
|
+
constraints[variable] = []
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_variables(variables, domains:)
|
43
|
+
variables.each do |variable|
|
44
|
+
add_variable(variable, domains: domains)
|
45
|
+
end
|
46
|
+
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_constraint(constraint = nil, variables: nil, &block)
|
51
|
+
validate_parameters(constraint, variables, block)
|
52
|
+
|
53
|
+
constraint = CustomConstraint.new(variables, block) if block
|
54
|
+
|
55
|
+
constraint.variables.each do |variable|
|
56
|
+
next constraints[variable] << constraint if constraints.include?(variable)
|
57
|
+
|
58
|
+
raise InvalidConstraintVariable,
|
59
|
+
"Constraint's variable doesn't exists in CSP"
|
60
|
+
end
|
61
|
+
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def add_ordering(ordering_algorithm)
|
66
|
+
@ordering_algorithm = ordering_algorithm
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_filtering(filtering_algorithm)
|
70
|
+
@filtering_algorithm = filtering_algorithm
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_lookahead(lookahead_algorithm)
|
74
|
+
@lookahead_algorithm = lookahead_algorithm
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def validate_parameters(constraint, variables, block)
|
80
|
+
if missing_both_constraint_and_block?(constraint, block)
|
81
|
+
raise ArgumentError, 'Either constraint or block must be provided'
|
82
|
+
end
|
83
|
+
if provided_both_constraint_and_block?(constraint, block)
|
84
|
+
raise ArgumentError, 'Both constraint and block cannot be provided at the same time'
|
85
|
+
end
|
86
|
+
if missing_variables_for_block?(block, variables)
|
87
|
+
raise ArgumentError, 'Variables must be provided when using a block'
|
88
|
+
end
|
89
|
+
return unless block_arity_exceeds_variables?(block, variables)
|
90
|
+
|
91
|
+
raise ArgumentError, 'Block should not have more arity than the quantity of variables'
|
92
|
+
end
|
93
|
+
|
94
|
+
def missing_both_constraint_and_block?(constraint, block)
|
95
|
+
constraint.nil? && block.nil?
|
96
|
+
end
|
97
|
+
|
98
|
+
def provided_both_constraint_and_block?(constraint, block)
|
99
|
+
constraint && block
|
100
|
+
end
|
101
|
+
|
102
|
+
def missing_variables_for_block?(block, variables)
|
103
|
+
block && variables.nil?
|
104
|
+
end
|
105
|
+
|
106
|
+
def block_arity_exceeds_variables?(block, variables)
|
107
|
+
!variables.nil? && block.arity > variables.length
|
108
|
+
end
|
109
|
+
|
110
|
+
def search_solution(assignment = {})
|
111
|
+
algorithm.backtracking(assignment)
|
112
|
+
end
|
113
|
+
|
114
|
+
def algorithm
|
115
|
+
Algorithms::Backtracking.new(
|
116
|
+
problem: self,
|
117
|
+
ordering_algorithm: ordering_algorithm,
|
118
|
+
filtering_algorithm: filtering_algorithm,
|
119
|
+
lookahead_algorithm: lookahead_algorithm,
|
120
|
+
max_solutions: max_solutions
|
121
|
+
)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/csp/utils.rb
ADDED
data/lib/csp/version.rb
ADDED
data/lib/csp-resolver.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'csp/version'
|
4
|
+
require_relative 'csp/constraint'
|
5
|
+
require_relative 'csp/utils'
|
6
|
+
require_relative 'csp/algorithms/filtering/no_filter'
|
7
|
+
require_relative 'csp/algorithms/ordering/no_order'
|
8
|
+
require_relative 'csp/algorithms/lookahead/no_algorithm'
|
9
|
+
require_relative 'csp/algorithms/lookahead/ac3'
|
10
|
+
require_relative 'csp/algorithms/backtracking'
|
11
|
+
require_relative 'csp/constraints'
|
12
|
+
require_relative 'csp/problem'
|
13
|
+
|
14
|
+
module CSP; end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: csp-resolver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- André Benjamim
|
8
|
+
- Gustavo Alberto
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2024-05-16 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: This Ruby gem solves CSPs using custom constraints
|
15
|
+
email:
|
16
|
+
- andre.benjamim@rebase.com.br
|
17
|
+
- gustavo.costa@rebase.com.br
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- ".rubocop.yml"
|
23
|
+
- MIT-LICENSE
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- csp-resolver.gemspec
|
27
|
+
- examples/event_scheduling.rb
|
28
|
+
- examples/map_coloring.rb
|
29
|
+
- examples/queen.rb
|
30
|
+
- examples/sculpture.rb
|
31
|
+
- lib/csp-resolver.rb
|
32
|
+
- lib/csp/algorithms/backtracking.rb
|
33
|
+
- lib/csp/algorithms/filtering/no_filter.rb
|
34
|
+
- lib/csp/algorithms/lookahead/ac3.rb
|
35
|
+
- lib/csp/algorithms/lookahead/no_algorithm.rb
|
36
|
+
- lib/csp/algorithms/ordering/no_order.rb
|
37
|
+
- lib/csp/constraint.rb
|
38
|
+
- lib/csp/constraints.rb
|
39
|
+
- lib/csp/problem.rb
|
40
|
+
- lib/csp/utils.rb
|
41
|
+
- lib/csp/version.rb
|
42
|
+
homepage: https://github.com/Rebase-BR/csp-resolver
|
43
|
+
licenses:
|
44
|
+
- MIT
|
45
|
+
metadata:
|
46
|
+
homepage_uri: https://github.com/Rebase-BR/csp-resolver
|
47
|
+
source_code_uri: https://github.com/Rebase-BR/csp-resolver
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 2.5.8
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubygems_version: 3.0.3
|
64
|
+
signing_key:
|
65
|
+
specification_version: 4
|
66
|
+
summary: A Ruby CSP Solver
|
67
|
+
test_files: []
|