473,659 Members | 2,662 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

Recursive generators and backtracking search

I've been using generators to implement backtracking search for a while
now. Unfortunately, my code is large and complex enough (doing
unification on math expressions) that its hard to post a simple
example. So I decided to look for a simpler problem that could be used
to demonstrate the technique that I am talking about.

I noticed that PEP 255 (Simple Generators) refers to an implementation
of the "8 Queens" problem in the lib/test directory. Looking at the
code, I see that while it does use generators, it doesn't use them
recursively.

As an alternative, I'd like to present the following implementation. If
you compare this one with the one in lib/test/test_generator. py you
will agree (I hope) that by using recursive generators to implement
backtracking, the resulting code is a little more straightforward and
intuitive:

# Example of using generators to implement backtracking search.
# The example below is an implementation of the classic "N queens"
# problem (place N queens on an N x N chessboard so that none are
# threatened by the others.)
#
# Board representation: Since no two queens can be one the same
# row, the board is represented as a tuple of N length, where
# each element is the column occupied by the queen on that row.

def queens( bsize ):

# Function to test if a queen is threatened by any previously
# placed queen.
def threaten( qarray, newpos ):
# Now check the diagonals
dist = len( qarray ) # Distance between rows
for q in qarray:
if q == newpos: return True # Same column
if q + dist == newpos: return True # diagonal
if q - dist == newpos: return True # diagonal
dist -= 1
return False

def qsearch( qarray = () ):
for q in range( 0, bsize ): # Try each position
if not threaten( qarray, q ): # If not threatened
pos = qarray + ( q, ); # Append to the pos array

if len( pos ) >= bsize: # Are we done?
yield pos # Yield the answer
else: # recursively call new generator
for pos in qsearch( pos ):
yield pos

print "Queens problem for", bsize, "x", bsize, "board."
for ans in qsearch():
` # Print out the board
print "+" + "---+" * bsize;
for q in ans:
print "|" + " |" * q + " Q |" + " |" * (bsize - q - 1)
print "+" + "---+" * bsize;
print

queens( 8 )

Now, you may be wondering what is my point? Well, first, I want to
encourage people to think about using Python as a language for complex
heuristic search problems. Traditionally, LISP and Prolog have been the
language of choices for "AI" type programming, however there is a clear
advantage to the readability and maintainability of Python, as well as
being much more integrated into modern computing environments (in terms
of available interpreters, IDEs, libraries, etc.)

Secondly, I want to lobby for additional support in the language and
standard libraries for handling such problems. There are a number of
fairly obvious language enhancements which would make the above example
even simpler - for examle, the idea of being able to return the output
of one generator directly from another instead of having to iterate
through all of the results and then re-yield them has already been
discussed in this forum.

Oct 29 '05 #1
6 4527
Talin <vi*****@gmail. com> wrote:
even simpler - for examle, the idea of being able to return the output
of one generator directly from another instead of having to iterate
through all of the results and then re-yield them has already been
discussed in this forum.


I missed those discussions, having been away from the group for awhile.
To me, the simplification of changing, e.g.,

for x in whatever_other_ iterable: yield x

into (say)

yield from whatever_other_ iterable

is minute and not worth changing the syntax (even though something like
'yield from' would mean no keywords would need to be added).
Alex
Oct 29 '05 #2
"Talin" <vi*****@gmail. com> writes:
As an alternative, I'd like to present the following implementation. If
you compare this one with the one in lib/test/test_generator. py you
will agree (I hope) that by using recursive generators to implement
backtracking, the resulting code is a little more straightforward and
intuitive:
I'd propose one change to that... def qsearch( qarray = () ):
for q in range( 0, bsize ): # Try each position
if not threaten( qarray, q ): # If not threatened
pos = qarray + ( q, ); # Append to the pos array

if len( pos ) >= bsize: # Are we done?
yield pos # Yield the answer
else: # recursively call new generator
for pos in qsearch( pos ):
yield pos


Um - do you really want to reuse the variable pos here? Yeah, it
works, but this strikes me as very confusing. I'm not sure that it
might not be implementation dependent.

<mike
--
Mike Meyer <mw*@mired.or g> http://www.mired.org/home/mwm/
Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.
Oct 29 '05 #3
"Talin" <viri...@gmail. com> wrote:
I've been using generators to implement backtracking search for a while
now. Unfortunately, my code is large and complex enough (doing
unification on math expressions) that its hard to post a simple
example. So I decided to look for a simpler problem that could be used
to demonstrate the technique that I am talking about.


Here are two even simpler problems that are solved elegantly (though
not necessarily efficiently) with recursive generators:

def cartesian_produ ct(*sequences):
'''Iterate over the elements of the cartesian product of zero or
more sequences.
for x in cartesian_produ ct(range(3), 'a', (3.25,-1.2)): ... print x
(0, 'a', 3.25)
(0, 'a', -1.2)
(1, 'a', 3.25)
(1, 'a', -1.2)
(2, 'a', 3.25)
(2, 'a', -1.2)
'''
if not sequences:
yield ()
else:
for item in sequences[0]:
head = (item,)
for tail in cartesian_produ ct(*sequences[1:]):
yield head + tail
def powerset(iterab le):
'''Iterate over all subsets of an iterable.
for s in powerset('abc') :

... print s
frozenset([])
frozenset(['a'])
frozenset(['b'])
frozenset(['a', 'b'])
frozenset(['c'])
frozenset(['a', 'c'])
frozenset(['c', 'b'])
frozenset(['a', 'c', 'b'])
'''
yield frozenset()
for s in _powerset(iter( iterable)):
yield s

def _powerset(itera tor):
first = frozenset([iterator.next()])
yield first
for s in _powerset(itera tor):
yield s
yield s | first
George

Oct 30 '05 #4
>> for pos in qsearch( pos ):
yield pos
Um - do you really want to reuse the variable pos here? Yeah, it
works, but this strikes me as very confusing. I'm not sure that it
might not be implementation dependent.


Certainly not. pos is - and that is standard python semantics - just a
name. Passing the bound _value_ of pos to some function and rebinding
the the name afterwards is perfectly legal and will work in all
implementations .

The question of style though remains. I wouldn't do that either, but
what I frequntly do is something like this:
pos = "10"
pos = int(pos)

Thus when I'm sort of transforming a value, I think it's ok, as the name
still refers to the same conceptual entity.

Regards,

Diez
Oct 30 '05 #5

Alex Martelli wrote:
for x in whatever_other_ iterable: yield x

into (say)

yield from whatever_other_ iterable

is minute and not worth changing the syntax (even though something like
'yield from' would mean no keywords would need to be added).


I agree that the improvement is minor, but I'll take what I can get.
Although, I can think that perhaps there could be a potential
efficiency improvement as well - right now, each result has to crawl
its way up the stack of yields. For an 8 x 8 board, each final result
gets yielded 8 times. A 'yield from' might potentially be able to
splice the results directly into the output of the generator using some
kind of iterator chain logic. I'm not sure how feasible this is,
however.

A more compelling benefit would be some means of yielding a value
across multiple stack frames. Currently the generator semantics are
limited, in that they only allow co-routine behavior between a caller
and its directly called subroutine.

What I am more interested in, however, is the use of backtracking
search in Python, and its application to more complex search problems.
The main benefit of the generator approach is in the elimination of
callbacks, allowing the consumer of the search results to retain its
local state in a convenient way, (Anyone who's ever struggled with the
SAX API for XML parsing knows what I am talking about.)

One difference between these more complex problems and the simple
example I posted is that it isn't just simple recursion at work. Each
level of search may invoke a variety of different search strategies for
each sub-part of the problem, which themselves will do the same with
their own portion.

Here's a thought experiment: What would it take to make Python the
premier language for AI research? (Not that I am proposing that this
should be the Python community's goal, this is more in the way of a
brainstorm session.)

Oct 30 '05 #6
[Talin]
I've been using generators to implement backtracking search for a while
now. Unfortunately, my code is large and complex enough (doing
unification on math expressions) that its hard to post a simple
example. So I decided to look for a simpler problem that could be used
to demonstrate the technique that I am talking about.

I noticed that PEP 255 (Simple Generators) refers to an implementation
of the "8 Queens" problem in the lib/test directory. Looking at the
code, I see that while it does use generators, it doesn't use them
recursively.
In context, the N-Queens and MxN Knight's Tour solvers in
test_generators .py are exercising the conjoin() generators in that
file. That's a different approach to backtracking search, with some
nice features too: (1) it uses heap space instead of stack space;
and, (2) it's easy to run entirely different code at different levels
of the search. #2 isn't well-illustrated by the N-Queens solver
because the problem is so symmetric, although it is used to give the
code for each row its own local table of the board lines used by the
squares in that row. That in turn is a major efficiency win. The
Knight's Tour solver makes more obvious use of #2, by, e.g., running
different code for "the first" square than for "the second" square
than for "the last" square than for "all the other" squares. That
doesn't require any runtime test-and-branch'ing in the search code,
it's set up once at the start in the list of M*N generators passed to
conjoin() (each square gets its own generator, which can be customized
in arbitrary ways, in advance, for that square).
As an alternative, I'd like to present the following implementation. If
you compare this one with the one in lib/test/test_generator. py you
will agree (I hope) that by using recursive generators to implement
backtracking, the resulting code is a little more straightforward and
intuitive:
Since "straightfo ward and intuitive" weren't the goals of the
test_generators .py implementations , that's not too surprising ;-)
...

Oct 31 '05 #7

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

8
2299
by: Paul Chiusano | last post by:
I've been playing around with generators and have run into a difficulty. Suppose I've defined a Node class like so: class Node: def __init__(self, data=None, left=None, right=None): self.children = self.children.append(left) self.children.append(right) self.data = data
19
2284
by: Carlos Ribeiro | last post by:
Hello all, Here I am using some deeply nested, tree-like data structures. In some situations I need to traverse the tree; the old-style way to do it is to write a recursive method on the node class, as in: def walk(self): """old-style recursive tree traversal""" child.do_something for child in childs:
1
1942
by: Steven Spear | last post by:
Hi. I am trying to perform backtracking with this search function. Something is going wrong when I enter 2 at the command line. Entering 1 at the command line seems to work fine. I notice that backtracking never takes place. Obviously there is something wrong with my logic. I have been trying many things for many days to correct the situation, but nothing seems to help. Does anyone have any suggestions? Thanks, Steve #include...
20
17033
by: Webdad | last post by:
Hi! I running my first year as industrial engineer (informatics) We have an assignment to do : .... create a playfield (matrix). Some places in that field are blocked, so you can't pass them. The others are free to go over ... (I already found that part) -> http://users.pandora.be/hebbrecht/jochen/c++/test.cpp
7
6122
by: Aloo | last post by:
Dear friends, If we declare a recursive function as 'inline' then does it actually convert to an iterative form during compilation ( the executable code is iterative)? Is it possible ? Please reply.
9
3317
by: seberino | last post by:
I'm a compiler newbie and curious if Python grammar is able to be parsed by a recursive descent parser or if it requires a more powerful algorithm. Chris
2
3431
by: ChuckB | last post by:
Ok, I'm working on this program to do recursive backtracking, however it's not working for all cases. Any help would be apprietiated. (all 4 should be true, but 168 is coming up false) Heres the rules: 1. If n is even, then you may give back exactly n/2 bears. 2. If n is divisible by 3 or 4, then you may multiply the last two digits of n and give back this many bears. By the way, the last digit of n is n%10, and the next-to-last digit...
18
4712
by: Just Another Victim of the Ambient Morality | last post by:
Is pyparsing really a recursive descent parser? I ask this because there are grammars it can't parse that my recursive descent parser would parse, should I have written one. For instance: from pyparsing import * grammar = OneOrMore(Word(alphas)) + Literal('end') grammar.parseString('First Second Third end')
0
8330
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it. First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
0
8850
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed. This is as boiled down as I can make it. Here is my compilation command: g++-12 -std=c++20 -Wnarrowing bit_field.cpp Here is the code in...
0
8746
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven tapestry of website design and digital marketing. It's not merely about having a website; it's about crafting an immersive digital experience that captivates audiences and drives business growth. The Art of Business Website Design Your website is...
0
7355
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own.... Now, this would greatly impact the work of software developers. The idea...
1
6178
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
5649
by: conductexam | last post by:
I have .net C# application in which I am extracting data from word file and save it in database particularly. To store word all data as it is I am converting the whole word file firstly in HTML and then checking html paragraph one by one. At the time of converting from word file to html my equations which are in the word document file was convert into image. Globals.ThisAddIn.Application.ActiveDocument.Select();...
0
4175
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
0
4334
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
2
1975
muto222
by: muto222 | last post by:
How can i add a mobile payment intergratation into php mysql website.

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.