constraint 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|