ambit 0.9.0

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.
@@ -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