ambit 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'ambit'
5
+
6
+ # This solution to the N queens problem is inspired by that given
7
+ #
8
+ # Sterling, Leon and Ehud Shapiro, The Art of Prolog, MIT Press, 1994
9
+ # http://www.amazon.com/Art-Prolog-Second-Programming-Techniques/dp/0262193388
10
+ #
11
+ # but is less elegant, as this is not prolog (and I am not Sterling or Shapiro)
12
+ #
13
+ # we want to place N queens on an NxN chess board. Since we know no two queens
14
+ # can be in the same row, an array of N integers between 0 and N-1 will do to
15
+ # represent the placement. Since we know no two queens can be in the same column,
16
+ # each number from 1 .. N will appear once in this array; this means the solution
17
+ # is a permutation of 1 .. N
18
+
19
+ # Here is the actual board generator. Next is the test if a position is safe. All else
20
+ # in this file is for display or testing.
21
+ $nd = Ambit::Generator.new
22
+ def queens n, board = []
23
+ if board.size == n
24
+ board
25
+ else
26
+ c = $nd.choose(1..n)
27
+ $nd.fail! unless safe board, c
28
+ queens n, board + [c]
29
+ end
30
+ end
31
+
32
+ # board is the first M columns of an NxN board, and is valid so far.
33
+ # piece is a proposed piece for the M+1th row of the board.
34
+ # returns true if piece is a valid placement, false otherwise
35
+ def safe board, piece
36
+ board.each_with_index do |c, r|
37
+ return false if c == piece # same column
38
+ # they're on the same diagonal if the distance in columns == the distance in rows
39
+ rdist = board.size - r
40
+ cdist = (piece - c).abs
41
+ return false if rdist == cdist
42
+ end
43
+ true
44
+ end
45
+
46
+ # Alternator is an infinite enumerables which flipflops between two values
47
+ # We use these to define an odd row and an even row as
48
+ # [" ", ".", " ", ...] and [".", " ", ".", ...], respectively.
49
+ # With these at our disposal, we can break off as many squares as we need to draw
50
+ # an even or odd board row of any size
51
+ class Alternator
52
+ include Enumerable
53
+ def initialize first, second
54
+ @first, @second = first, second
55
+ end
56
+
57
+ def each
58
+ loop do
59
+ yield @first
60
+ yield @second
61
+ end
62
+ end
63
+ end
64
+
65
+ E = Alternator.new " ", "."
66
+ O = Alternator.new ".", " "
67
+
68
+ # return a blank board of a given size, as an array of N strings of length N
69
+ def empty_board n
70
+ (1..n).collect do |i|
71
+ ((i+1).odd? ? O : E).take(n).to_s
72
+ end
73
+ end
74
+ # board is a board in the above format (array where a[i] is the column
75
+ # number of the queen in row i+1, rows and columns being numbered from 1
76
+
77
+ # n, if provided, is the size of the full board. This allows this routine
78
+ # to show a partly-filled board by passing n > board.size, though we don't
79
+ # currently use this.
80
+ def board_to_s board, n = board.size
81
+ b = empty_board n
82
+ board.each_with_index do |x, i|
83
+ b[i][x-1] = 'Q'
84
+ end
85
+ b.join "\n"
86
+ end
87
+
88
+ def show_board board
89
+ puts board_to_s board
90
+ end
91
+
92
+ # tests:
93
+ # show_board (1..8).to_a
94
+ # puts ""
95
+ # show_board [1, 3, 5, 7, 2, 4, 6, 8]
96
+ raise "board_to_s failed" unless board_to_s([1,2]) == "Q.\n.Q"
97
+
98
+ # tests:
99
+ raise "safe failed" if safe([1, 3, 5], 3)
100
+ raise "safe failed" unless safe([1, 3, 5], 2)
101
+ raise "safe failed" if safe([1, 3, 5], 4)
102
+
103
+ # to run one board
104
+ #show_board queens 8
105
+
106
+ # to show all valid 8x8 boards:
107
+ args = ARGV.empty? ? ["8"] : ARGV
108
+ args.each do |a|
109
+ begin
110
+ n = a.to_i
111
+
112
+ # state is not reset when we fail!, so count can be used to track how many times
113
+ # we returned from `show_board queens n' below
114
+ count = 0
115
+ show_board queens n
116
+ count += 1
117
+ puts ""
118
+
119
+ # force next solution; will throw ChoicesExhausted if none found
120
+ $nd.fail!
121
+
122
+ rescue Ambit::ChoicesExhausted
123
+ # thrown when we finally run out of possible boards, whether we found any valid
124
+ # boards or not (since we unconditionally fail! above)
125
+ puts "#{count} #{n}x#{n} boards found, not accounting for symmetry"
126
+ end
127
+ end
@@ -0,0 +1,101 @@
1
+ # This gem allows choose/fail (amb) style non-deterministic programming in
2
+ # Ruby
3
+ #
4
+ # Author:: Jim Wise (mailto:jwise@draga.com)
5
+ # Copyright:: Copyright (c) 2011 Jim Wise
6
+ # License:: 2-clause BSD-Style (see LICENSE[link:files/LICENSE.html])
7
+
8
+ module Ambit
9
+
10
+ VERSION = '0.9.0'
11
+
12
+ # A ChoicesExhausted exception is raised if the outermost choose invocation of
13
+ # a Generator has run out of choices, indicating that no (more) solutions are possible.
14
+ class ChoicesExhausted < StandardError
15
+ end
16
+
17
+ class Generator
18
+ # Allocate a new private Generator. Usually not needed -- use Ambit::choose et al, instead.
19
+ #
20
+ # See "Private Generators" in the README for details
21
+ def initialize
22
+ @paths = []
23
+ end
24
+
25
+ # Clear all outstanding choices registered with this generator.
26
+ #
27
+ # Returns the generator to the state it was in before all choices were
28
+ # made. Does not rewind execution.
29
+ def clear!
30
+ @paths = []
31
+ end
32
+
33
+ # Given an enumerator, begin a generate-and-test process.
34
+ #
35
+ # Returns with the first member of the enumerator. A later call to #fail!
36
+ # on the same generator will backtrack and try the next value in the
37
+ # enumerator, continuing from the point of this #choose as if that value
38
+ # had been chosen originally.
39
+ #
40
+ # Multiple calls to #choose will nest, so that backtracking forms
41
+ # a tree-like execution path
42
+ #
43
+ # calling #choose with no argument or an empty iterator
44
+ # is equivalent to calling #fail!
45
+ def choose choices = []
46
+ ch = choices.clone # clone it in case it's modified by the caller
47
+ ch.each do |choice|
48
+ callcc do |cc|
49
+ @paths.unshift cc
50
+ return choice
51
+ end
52
+ end
53
+ self.fail! # if we get here, we've exhausted the choices
54
+ end
55
+
56
+ alias amb choose
57
+
58
+ # Indicate that the current combination of choices has failed, and roll execution back
59
+ # to the last #choose, continuing with the next choice.
60
+ def fail!
61
+ raise ChoicesExhausted.new if @paths.empty?
62
+ cc = @paths.shift
63
+ # if it quacks (or can be called) like a duck, call it -- it's either a Proc from #mark or a Continuation from #choose
64
+ cc.call
65
+ end
66
+
67
+ def assert cond
68
+ fail! unless cond
69
+ end
70
+
71
+ alias require assert
72
+
73
+ # Begin a mark/cut pair to commit to one branch of the current #choose operation.
74
+ #
75
+ # See "Marking and Cutting" in README for details
76
+ def mark
77
+ @paths.unshift Proc.new {self.fail!}
78
+ end
79
+
80
+ # Commit to all choices since the last #mark! operation.
81
+ #
82
+ # See "Marking and Cutting" in README for details
83
+ def cut!
84
+ return if @paths.empty?
85
+ # rewind paths back to the last mark
86
+ @paths = @paths.drop_while {|x| x.instance_of? Continuation}
87
+ # drop up to one mark
88
+ @paths = @paths.drop(1) unless @paths.empty?
89
+ end
90
+ end
91
+
92
+ # forward method invocations on this module to the default Generator.
93
+ def Ambit::method_missing(sym, *args, &block) # :nodoc:
94
+ Ambit::Default_Generator.send(sym, *args, &block)
95
+ end
96
+
97
+ # The default generator used by Ambit.choose, Ambit.fail!, et al.
98
+ # should not be used directly.
99
+ Default_Generator = Generator.new
100
+
101
+ end
@@ -0,0 +1,68 @@
1
+ require "test/unit"
2
+ require "ambit"
3
+
4
+ class TestAmbit < Test::Unit::TestCase
5
+
6
+ def test_simple_default
7
+ x = Ambit::choose([1, 2])
8
+ Ambit::require x.even?
9
+ assert(x == 2)
10
+ Ambit::clear!
11
+ end
12
+
13
+ def test_simple_private
14
+ nd = Ambit::Generator.new
15
+ x = nd.choose([1, 2])
16
+ nd.require x.even?
17
+ assert(x == 2)
18
+ end
19
+
20
+ def test_nested_default
21
+ a = Ambit::choose(0..5)
22
+ b = Ambit::choose(0..5)
23
+ Ambit::fail! unless a + b == 7
24
+ assert(a+b == 7)
25
+ Ambit::clear!
26
+ end
27
+
28
+ def test_nested_private
29
+ nd = Ambit::Generator.new
30
+ a = nd.choose(0..5)
31
+ b = nd.choose(0..5)
32
+ nd.fail! unless a + b == 7
33
+ assert(a+b == 7)
34
+ nd.clear!
35
+ end
36
+
37
+ def test_toplevel_fail
38
+ assert_raise Ambit::ChoicesExhausted do
39
+ Ambit::fail!
40
+ end
41
+ end
42
+
43
+ def test_clear
44
+ Ambit::choose(0..10)
45
+ Ambit::choose(0..1)
46
+ Ambit::clear!
47
+ assert_raise Ambit::ChoicesExhausted do
48
+ Ambit::fail!
49
+ end
50
+ end
51
+
52
+ def test_mark_cut
53
+ nd = Ambit::Generator.new
54
+ i = 0;
55
+ a = nd.choose(1..3)
56
+ nd.mark
57
+ b = nd.choose([1, 2, 3])
58
+ c = nd.choose([1, 2, 3])
59
+ i+=1
60
+ if b == 2 && c == 2
61
+ nd.cut!
62
+ end
63
+ nd.fail!
64
+ rescue Ambit::ChoicesExhausted
65
+ assert(i==15)
66
+ end
67
+
68
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ambit
3
+ version: !ruby/object:Gem::Version
4
+ hash: 59
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 9
9
+ - 0
10
+ version: 0.9.0
11
+ platform: ruby
12
+ authors:
13
+ - Jim Wise
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-26 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: hoe
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 35
29
+ segments:
30
+ - 2
31
+ - 9
32
+ - 4
33
+ version: 2.9.4
34
+ type: :development
35
+ version_requirements: *id001
36
+ description: |-
37
+ This is an all-ruby implementation of choose/fail nondeterministic
38
+ programming with branch cut, as described in Chapter 22 of Paul Graham's
39
+ <em>On Lisp</em>[1], Chapter, or Section 4.3 of <em>SICP</em>[2].
40
+
41
+ Due to Ruby containing a true call/cc, this is a much straighter port of
42
+ Paul Graham's scheme version of this code than his Common Lisp or my C
43
+ versions are. :-)
44
+ email:
45
+ - jwise@draga.com
46
+ executables: []
47
+
48
+ extensions: []
49
+
50
+ extra_rdoc_files:
51
+ - History.txt
52
+ - LICENSE.txt
53
+ - Manifest.txt
54
+ - README.txt
55
+ files:
56
+ - .autotest
57
+ - History.txt
58
+ - LICENSE.txt
59
+ - Manifest.txt
60
+ - README.rdoc
61
+ - README.txt
62
+ - Rakefile
63
+ - examples/example.rb
64
+ - examples/queens.rb
65
+ - lib/ambit.rb
66
+ - test/test_ambit.rb
67
+ - .gemtest
68
+ homepage: https://github.com/jimwise/ruby/tree/master/ambit
69
+ licenses: []
70
+
71
+ post_install_message:
72
+ rdoc_options:
73
+ - --main
74
+ - README.txt
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ hash: 3
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ requirements: []
96
+
97
+ rubyforge_project: ambit
98
+ rubygems_version: 1.7.2
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: This is an all-ruby implementation of choose/fail nondeterministic programming with branch cut, as described in Chapter 22 of Paul Graham's <em>On Lisp</em>[1], Chapter, or Section 4.3 of <em>SICP</em>[2]
102
+ test_files:
103
+ - test/test_ambit.rb