constraint 0.1 → 0.2
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.
- data/README.TXT +115 -32
- data/VERSION.TXT +1 -1
- data/examples/in_range.rb +72 -0
- data/examples/primes.rb +97 -0
- data/examples/queens.rb +108 -0
- data/examples/queens_ca.rb +83 -0
- data/examples/queens_cb.rb +82 -0
- data/examples/queens_s.rb +85 -0
- data/examples/run_queens.sh +5 -0
- data/lib/constraint.rb +294 -110
- data/test/tc_contraint.rb +88 -42
- metadata +9 -2
data/README.TXT
CHANGED
@@ -1,37 +1,48 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
:title: Constraint -- Ensure that objects always satisfy a set of constraints
|
2
|
+
|
3
|
+
|
4
|
+
This library provides a way to ensure that certain objects always
|
5
|
+
satisfy a specified set of constraints. An object that can be
|
6
|
+
constrained must be a child of <tt>Constraint::Shell</tt>, which acts as
|
7
|
+
a wrapper for the actual value. Constraints can added to subclasses and
|
8
|
+
its instances (per-object constraints). It is possible to define methods
|
9
|
+
that handle constraint violations and retrofit the object according to
|
10
|
+
its definition.
|
11
|
+
|
12
|
+
With some help from the developer, this library can also be used (to
|
13
|
+
some extent at least) to generate correct values and to find solutions
|
14
|
+
fitting the set of constraints.
|
6
15
|
|
7
16
|
Project:: http://rubyforge.org/projects/constraint/
|
8
17
|
Download:: http://rubyforge.org/frs/?group_id=748
|
9
18
|
Support:: http://rubyforge.org/forum/?group_id=748
|
10
19
|
|
11
|
-
In my experience, a certain type of
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
In my experience, a certain type of error in dynamically typed languages
|
21
|
+
is caused by pushing a wrong object to a collection or similar. What
|
22
|
+
makes this type of error so awkward is that they often result in an
|
23
|
+
exception in a different part of your program, which is why it can
|
24
|
+
sometimes be unnecessarily difficult to track down what is actually
|
25
|
+
causing the problem. The goal of this library is to raise an exception
|
26
|
+
right where such a thing happens. While ruby is sometimes a little bit
|
27
|
+
hesitant in raising exceptions, this library tries to gently counteract
|
28
|
+
this attitude a little.
|
20
29
|
|
21
30
|
Be aware that this library introduces yet another level of indirection
|
22
|
-
and yet additional runtime checks
|
31
|
+
and yet additional runtime checks which slows things down a little. If
|
23
32
|
you define CONSTRAINT_DISABLE before requiring the library, constraint
|
24
33
|
checks will be deactivated.
|
25
34
|
|
26
35
|
|
27
|
-
=
|
36
|
+
= Examples
|
28
37
|
|
29
38
|
Constrained classes should inherit from <tt>Constraint::Shell</tt>. The
|
30
39
|
subclass should provide a way for defining new instances (by defining
|
31
40
|
#new_constrained) and redefine #process_constrained_value. The
|
32
41
|
@constrained_value instance variable contains the shell's actual values.
|
33
42
|
|
34
|
-
|
43
|
+
== Basic use
|
44
|
+
|
45
|
+
require 'constraint'
|
35
46
|
|
36
47
|
class ConstrainedArray < Constraint::Shell
|
37
48
|
def new_constrained
|
@@ -44,38 +55,110 @@ Examples:
|
|
44
55
|
end
|
45
56
|
|
46
57
|
class NumericArray < ConstrainedArray
|
47
|
-
or_constraint("Numeric") {|e| e.kind_of?(Numeric)}
|
58
|
+
or_constraint("Numeric") {|o, e| e.kind_of?(Numeric)}
|
48
59
|
end
|
49
60
|
|
50
61
|
class EvenNumericArray < NumericArray
|
51
|
-
and_constraint("Even") {|e| e.modulo(2) == 0}
|
62
|
+
and_constraint("Even") {|o, e| e.modulo(2) == 0}
|
52
63
|
end
|
53
64
|
|
65
|
+
enarr = EvenNumericArray.new([2,4,6])
|
66
|
+
enarr
|
67
|
+
=> [2, 4, 6]
|
68
|
+
enarr << 8
|
69
|
+
=> [2, 4, 6, 8]
|
70
|
+
enarr << 9
|
71
|
+
(irb):23:in `irb_binding': Fixnum didn't meet constraint 'Even': 9 (ConstraintViolation)
|
72
|
+
|
73
|
+
|
54
74
|
class EvenInteger < Constraint::Shell
|
55
|
-
and_constraint("Numeric") {|e| e.kind_of?(Integer)}
|
56
|
-
and_constraint("Even") {|e| e.modulo(2) == 0}
|
75
|
+
and_constraint("Numeric") {|o, e| e.kind_of?(Integer)}
|
76
|
+
and_constraint("Even") {|o, e| e.modulo(2) == 0}
|
57
77
|
end
|
58
78
|
|
59
79
|
enum = EvenInteger.new(2)
|
60
|
-
enum.and_constraint("LT10") {|e| e < 10}
|
80
|
+
enum.and_constraint("LT10") {|o, e| e < 10}
|
81
|
+
|
82
|
+
enum + 2
|
83
|
+
=> 4
|
84
|
+
|
85
|
+
enum + 1
|
86
|
+
(irb):11:in `irb_binding': Fixnum didn't meet constraint 'Even': 3 (ConstraintViolation)
|
87
|
+
|
88
|
+
enum.on_constraint_violation('Even') {|o, e| (e / 2).round * 2}
|
89
|
+
enum + 1
|
90
|
+
=> 2
|
91
|
+
|
61
92
|
|
62
|
-
|
93
|
+
== Duckismo
|
63
94
|
|
64
95
|
class DuckPalace < ConstrainedArray
|
65
|
-
and_constraint('Quack') {|e| e.do_you_quack?}
|
66
|
-
and_constraint('Ticket') {|e| e.your_ticket_please == :valid}
|
96
|
+
and_constraint('Quack') {|o, e| e.do_you_quack?}
|
97
|
+
and_constraint('Ticket') {|o, e| e.your_ticket_please == :valid}
|
67
98
|
end
|
68
99
|
|
69
100
|
whiteduckpalace = DuckPalace.new
|
70
|
-
whiteduckpalace.and_constraint('White') {|e| e.colour == 'white'}
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
101
|
+
whiteduckpalace.and_constraint('White') {|o, e| e.colour == 'white'}
|
102
|
+
whiteduckpalace.on_constraint_violation('White') do |o, e|
|
103
|
+
if e.respond_to?(:paint)
|
104
|
+
e.paint('white')
|
105
|
+
else
|
106
|
+
raise ConstraintViolation
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
== Generators
|
112
|
+
|
113
|
+
With some help from the programmer, +Constraint+ can generate valid
|
114
|
+
values and collect possible solutions fulfilling a set of constraints.
|
115
|
+
The only prerequisite is to define the method
|
116
|
+
#generate_constrained_value that gives the next value in line (starting
|
117
|
+
from a value given as argument, which can be nil) or raises a
|
118
|
+
Constraint::NoMoreValues exception if no more values are available. This
|
119
|
+
isn't the most sophisticated solution but it may be useful in some
|
120
|
+
situations.
|
121
|
+
|
122
|
+
Here is a basic example that illustrates this use of the library:
|
123
|
+
|
124
|
+
class NumInRange < Constraint::Shell
|
125
|
+
constraint_attr :min, 0
|
126
|
+
constraint_attr :max, nil
|
127
|
+
|
128
|
+
and_constraint('Numeric') {|o, e| e.kind_of?(Numeric)}
|
129
|
+
and_constraint('InRange') {|o, e| o.min <= e && (!o.max || o.max >= e)}
|
130
|
+
on_constraint_violation('InRange') {|o, e| (o.max && e > o.max) ? o.max : o.min}
|
131
|
+
|
132
|
+
def generate_constrained_value(value)
|
133
|
+
if value
|
134
|
+
if !max or @max > value
|
135
|
+
value + 1
|
136
|
+
else
|
137
|
+
raise Constraint::NoMoreValues
|
138
|
+
end
|
139
|
+
else
|
140
|
+
@min
|
76
141
|
end
|
77
|
-
return super
|
78
142
|
end
|
79
143
|
end
|
80
144
|
|
145
|
+
class Num1To10 < NumInRange
|
146
|
+
@min = 1
|
147
|
+
@max = 10
|
148
|
+
end
|
149
|
+
|
150
|
+
p Num1To10.new
|
151
|
+
=> 1
|
152
|
+
p Num1To10.new(-10)
|
153
|
+
=> 1
|
154
|
+
p Num1To10.new(5.2) + 2.2
|
155
|
+
=> 7.4
|
156
|
+
p Num1To10.new(5.2) + 20.2
|
157
|
+
=> 10
|
158
|
+
p Num1To10.new.collect_constrained_values
|
159
|
+
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
160
|
+
p Num1To10.new(5).collect_constrained_values
|
161
|
+
=> [5, 6, 7, 8, 9, 10]
|
162
|
+
p Num1To10.new(5).collect_constrained_values_until {|e| e == 8}
|
163
|
+
=> [5, 6, 7]
|
81
164
|
|
data/VERSION.TXT
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2
|
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# in_range.rb
|
3
|
+
# @Author: Thomas Link
|
4
|
+
# @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
|
5
|
+
# @Created: 14-Jun-2005.
|
6
|
+
# @Last Change: 14-Jun-2005.
|
7
|
+
# @Revision: 0.135
|
8
|
+
#
|
9
|
+
# = Description
|
10
|
+
# = Usage
|
11
|
+
# = TODO
|
12
|
+
# = CHANGES
|
13
|
+
|
14
|
+
require 'constraint'
|
15
|
+
|
16
|
+
class NumInRange < Constraint::Shell
|
17
|
+
constraint_attr :min, 0
|
18
|
+
constraint_attr :max, nil
|
19
|
+
|
20
|
+
and_constraint('Numeric') {|o, e| e.kind_of?(Numeric)}
|
21
|
+
and_constraint('InRange') {|o, e| o.min <= e && (!o.max || o.max >= e)}
|
22
|
+
on_constraint_violation('InRange') {|o, e| (o.max && e > o.max) ? o.max : o.min}
|
23
|
+
|
24
|
+
def generate_constrained_value(value)
|
25
|
+
if value
|
26
|
+
if !max or @max > value
|
27
|
+
value + 1
|
28
|
+
else
|
29
|
+
raise Constraint::NoMoreValues
|
30
|
+
end
|
31
|
+
else
|
32
|
+
@min
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
if __FILE__ == $0
|
39
|
+
p "--- NumInRange"
|
40
|
+
p n = NumInRange.new
|
41
|
+
p n.max = 10
|
42
|
+
p n.collect_constrained_values
|
43
|
+
|
44
|
+
p n = NumInRange.new(5)
|
45
|
+
p n.max = 10
|
46
|
+
p n.collect_constrained_values
|
47
|
+
p n.max
|
48
|
+
p (n + 20).max
|
49
|
+
|
50
|
+
p "--- Num1To10"
|
51
|
+
class Num1To10 < NumInRange
|
52
|
+
@min = 1
|
53
|
+
@max = 10
|
54
|
+
end
|
55
|
+
|
56
|
+
p Num1To10.new
|
57
|
+
p Num1To10.new(1)
|
58
|
+
p Num1To10.new(5).collect_constrained_values
|
59
|
+
p Num1To10.new(5).collect_constrained_values_until {|e| e == 8}
|
60
|
+
p Num1To10.new(10)
|
61
|
+
p Num1To10.new(0)
|
62
|
+
p Num1To10.new(20)
|
63
|
+
|
64
|
+
p Num1To10.new(1.3)
|
65
|
+
p Num1To10.new(5.3)
|
66
|
+
p Num1To10.new(0.3)
|
67
|
+
p Num1To10.new(10.3)
|
68
|
+
p Num1To10.new(-10)
|
69
|
+
p Num1To10.new(5.2) + 2.2
|
70
|
+
p Num1To10.new(5.2) + 20.2
|
71
|
+
end
|
72
|
+
|
data/examples/primes.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# primes.rb
|
3
|
+
# @Author: Thomas Link (samul@web.de)
|
4
|
+
# @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
|
5
|
+
# @Created: 12-Jun-2005.
|
6
|
+
# @Last Change: 12-Jun-2005.
|
7
|
+
# @Revision: 0.178
|
8
|
+
|
9
|
+
require 'constraint'
|
10
|
+
|
11
|
+
# Primes:
|
12
|
+
class Prime < Constraint::Shell
|
13
|
+
@@primes = [2, 3]
|
14
|
+
@@noprimes = [4, 6, 9]
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def collect_primes(n)
|
18
|
+
pr = Prime.new(n)
|
19
|
+
pr.instance_eval do
|
20
|
+
@@primes[0..@@primes.index(pr.constrained_value)]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def new_constrained
|
26
|
+
generate_constrained_value(nil)
|
27
|
+
end
|
28
|
+
|
29
|
+
def generate_constrained_value(value)
|
30
|
+
if !value
|
31
|
+
return 2
|
32
|
+
elsif value <= 2
|
33
|
+
super
|
34
|
+
elsif value == 3
|
35
|
+
return 2
|
36
|
+
else
|
37
|
+
if value.modulo(2) == 0
|
38
|
+
return value - 1
|
39
|
+
else
|
40
|
+
return value - 2
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_noprime(n)
|
46
|
+
add_this_noprime(n)
|
47
|
+
for pr in @@primes[0..10]
|
48
|
+
nn = n * pr
|
49
|
+
add_this_noprime(nn)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_this_noprime(n)
|
54
|
+
unless @@noprimes.include?(n)
|
55
|
+
@@noprimes << n
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def check_prime(n)
|
60
|
+
next_constrained_value!(n)
|
61
|
+
for pr in @@primes
|
62
|
+
if pr > n
|
63
|
+
break
|
64
|
+
elsif n.modulo(pr) == 0
|
65
|
+
add_noprime(n)
|
66
|
+
return false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
@@primes << n
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
73
|
+
and_constraint('KnownNoPrime') {|p, n| !@@noprimes.include?(n)}
|
74
|
+
and_constraint('KnownPrime') {|p, n| @@primes.include?(n)}
|
75
|
+
or_constraint('Prime') {|p, n| p.check_prime(n)}
|
76
|
+
on_constraint_violation('Prime', 'KnownNoPrime') do |o, n|
|
77
|
+
o.next_constrained_value!(n).constrained_value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
if __FILE__ == $0
|
83
|
+
p (p = Prime.new(3))
|
84
|
+
p p.next_constrained_value!
|
85
|
+
|
86
|
+
p Prime.new(100)
|
87
|
+
p Prime.collect_primes(100)
|
88
|
+
|
89
|
+
def test_prime(n)
|
90
|
+
puts "#{n} => #{Prime.new(n).inspect}"
|
91
|
+
end
|
92
|
+
|
93
|
+
for i in 3..20
|
94
|
+
test_prime(i)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
data/examples/queens.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# queens.rb
|
3
|
+
# @Author: Thomas Link (samul@web.de)
|
4
|
+
# @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
|
5
|
+
# @Created: 11-Jun-2005.
|
6
|
+
# @Last Change: 12-Jun-2005.
|
7
|
+
# @Revision: 0.190
|
8
|
+
#
|
9
|
+
# = Description
|
10
|
+
# = Usage
|
11
|
+
# = TODO
|
12
|
+
# = CHANGES
|
13
|
+
|
14
|
+
require 'constraint'
|
15
|
+
|
16
|
+
QUEEN_MIN = 1
|
17
|
+
QUEEN_MAX = 5
|
18
|
+
|
19
|
+
class Queen < Constraint::Shell
|
20
|
+
# or_constraint('Numeric') {|q, e| e.kind_of?(Numeric)}
|
21
|
+
# or_constraint('Range') {|q, e| e >= QUEEN_MIN && e <= QUEEN_MAX}
|
22
|
+
|
23
|
+
def strike_any?(x, board)
|
24
|
+
board.each_with_index do |q, ox0|
|
25
|
+
ox = ox0 + 1
|
26
|
+
if x != ox
|
27
|
+
y = @constrained_value
|
28
|
+
oy = q.constrained_value
|
29
|
+
if y == oy or ((y - oy).abs == (x - ox).abs)
|
30
|
+
# p "DBG #{x}x#{y} #{ox}x#{oy} #{y-oy} #{x-ox}"
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
return false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class QueensBoard < Constraint::Shell
|
40
|
+
def new_constrained
|
41
|
+
@boards = []
|
42
|
+
@masks = []
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def process_constrained_value
|
47
|
+
if @constrained_value
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def new_board(pos)
|
53
|
+
unless (b = @boards[pos])
|
54
|
+
b = []
|
55
|
+
QUEEN_MAX.downto(1) do |x|
|
56
|
+
xx, pos = pos.divmod(QUEEN_MAX ** (x - 1))
|
57
|
+
b << Queen.new(xx + 1)
|
58
|
+
end
|
59
|
+
@boards[pos] = b
|
60
|
+
end
|
61
|
+
b
|
62
|
+
end
|
63
|
+
|
64
|
+
def board
|
65
|
+
new_board(@constrained_value)
|
66
|
+
end
|
67
|
+
|
68
|
+
or_constraint('Good') do |qb, board|
|
69
|
+
begin
|
70
|
+
b = qb.new_board(board)
|
71
|
+
rescue Exception => e
|
72
|
+
p e
|
73
|
+
end
|
74
|
+
# p b.collect {|e| e.constrained_value}
|
75
|
+
rv = true
|
76
|
+
b.each_with_index do |e, i|
|
77
|
+
if e.strike_any?(i + 1, b)
|
78
|
+
rv = false
|
79
|
+
break
|
80
|
+
end
|
81
|
+
end
|
82
|
+
rv
|
83
|
+
end
|
84
|
+
|
85
|
+
def generate_constrained_value(value)
|
86
|
+
if !value
|
87
|
+
0
|
88
|
+
elsif value < (QUEEN_MAX ** QUEEN_MAX)
|
89
|
+
value + 1
|
90
|
+
else
|
91
|
+
super
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
if __FILE__ == $0
|
98
|
+
b = QueensBoard.new
|
99
|
+
|
100
|
+
puts "First solution:"
|
101
|
+
p b.next_constrained_value!.board.collect {|e| e.constrained_value}
|
102
|
+
|
103
|
+
puts "All Solutions:"
|
104
|
+
for board in b.collect_constrained_values_until {|e| e >= (QUEEN_MAX ** QUEEN_MAX) - 2}
|
105
|
+
p board.board.collect {|e| e.constrained_value}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|