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 CHANGED
@@ -1,37 +1,48 @@
1
- This library provides a way to ensure that object always satisfy a
2
- specified set of constraints. An object that can be constrained must be
3
- of kind <tt>Constraint::Shell</tt>. Constraints can be added to classes
4
- and single objects. It is possible to define methods that handle
5
- constraint violations and can make the object fit the demands.
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 errors in dynamically typed
12
- languages are caused by pushing some wrong object to a collection type
13
- of object. What makes this type of error so awkward is that they often
14
- result in an exception in some totally different part of your program,
15
- which is why it sometimes can be quite hard to track down what is
16
- actually causing the problem. The goal of this library is to raise an
17
- exception right where such a thing happens. While ruby is sometimes a
18
- little bit hesitant in raising exceptions, this library tries to
19
- counteract this tendency a little bit.
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 what slows things down a little. If
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
- = Documentation
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
- Examples:
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
- or:
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
- class << whiteduckpalace
72
- def handle_constraint_violation(contraint_name, object)
73
- case contraint_name
74
- when 'white'
75
- return object.paint_me('white') if object.respond_to?(:paint_me)
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
 
@@ -1 +1 @@
1
- 0.1
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
+
@@ -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
+
@@ -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
+