ambit 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|