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.
- data/.autotest +23 -0
- data/.gemtest +0 -0
- data/History.txt +3 -0
- data/LICENSE.txt +28 -0
- data/Manifest.txt +11 -0
- data/README.rdoc +427 -0
- data/README.txt +427 -0
- data/Rakefile +12 -0
- data/examples/example.rb +61 -0
- data/examples/queens.rb +127 -0
- data/lib/ambit.rb +101 -0
- data/test/test_ambit.rb +68 -0
- metadata +103 -0
data/examples/queens.rb
ADDED
@@ -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
|
data/lib/ambit.rb
ADDED
@@ -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
|
data/test/test_ambit.rb
ADDED
@@ -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
|