Re: [Tutor] recursive generator

2010-03-07 Thread spir
On Sun, 7 Mar 2010 17:20:07 +0100
Hugo Arts  wrote:

> On Sun, Mar 7, 2010 at 1:58 PM, spir  wrote:
> > Hello,
> >
> > Is it possible at all to have a recursive generator? I think at a iterator 
> > for a recursive data structure (here, a trie). The following code does not 
> > work: it only yields a single value. Like if child.__iter__() were never 
> > called.
> >
> >    def __iter__(self):
> >        ''' Iteration on (key,value) pairs. '''
> >        print '*',
> >        if self.holdsEntry:
> >            yield (self.key,self.value)
> >        for child in self.children:
> >            print "<",
> >            child.__iter__()
> >            print ">",
> >        raise StopIteration
> >
> > With the debug prints in code above, "for e in t: print e" outputs:
> >
> > * ('', 0)
> > < > < > < > < > < > < > < > < >
> >
> > print len(t.children) # --> 9
> >
> > Why is no child.__iter__() executed at all? I imagine this can be caused by 
> > the special feature of a generator recording current execution point. (But 
> > then, is it at all possible to have a call in a generator? Or does the 
> > issue only appear whan the callee is a generator itself?) Else, the must be 
> > an obvious error in my code.
> >
> >
> > Denis
> 
> remember that child.__iter__ returns a generator object. Merely
> calling the function won't do anything. To actually get elements from
> a generator object, you need to call next() on the returned iterator,
> or iterate over it in another way (e.g. a for loop). I would write
> this method more or less like so:
> 
> from itertools import chain
> 
> def __iter__(self):
> this_entry = [(self.key, self.value)] is self.holds_entry else []
> return chain(this_entry, *[iter(c) for c in self.children])
> 
> (Warning! untested! I'm pretty sure chain will work like this, but you
> might have to do a little tweaking)

@ Hugo & Steven
Thank you very much. I'll study your proposals as soon as I can, then tell you 
about the results.
In the meanwhile, I (recursively) constructed the collection of entries and 
returned iter(collection). [This works, indeed, but I also want do know how to 
properly build a recursive iterator.]

Denis
-- 


la vita e estrany

spir.wikidot.com

___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] recursive generator

2010-03-07 Thread Rich Lovely
On 7 March 2010 12:58, spir  wrote:
> Hello,
>
> Is it possible at all to have a recursive generator? I think at a iterator 
> for a recursive data structure (here, a trie). The following code does not 
> work: it only yields a single value. Like if child.__iter__() were never 
> called.
>
>    def __iter__(self):
>        ''' Iteration on (key,value) pairs. '''
>        print '*',
>        if self.holdsEntry:
>            yield (self.key,self.value)
>        for child in self.children:
>            print "<",
>            child.__iter__()
>            print ">",
>        raise StopIteration
>
> With the debug prints in code above, "for e in t: print e" outputs:
>
> * ('', 0)
> < > < > < > < > < > < > < > < >
>
> print len(t.children) # --> 9
>
> Why is no child.__iter__() executed at all? I imagine this can be caused by 
> the special feature of a generator recording current execution point. (But 
> then, is it at all possible to have a call in a generator? Or does the issue 
> only appear whan the callee is a generator itself?) Else, the must be an 
> obvious error in my code.
>
>
> Denis
> --
> 
>
> la vita e estrany
>
> spir.wikidot.com
>
> ___
> Tutor maillist  -  tu...@python.org
> To unsubscribe or change subscription options:
> http://mail.python.org/mailman/listinfo/tutor
>

You are calling child.__iter__(), but it's return value is being thrown away.

What you want to be doing is something like

def __iter__(self):
if self.holdsEntry:
yield self.entry
for child in self.children:
print "<"
for val in child: #implicit call to child.__iter__()
yield val
print ">"

Then, when the child.__iter__() is called, the returned iterator is
iterated, and the values are passed up the call stack.  There's
probably a more terse way of doing this using itertools, but I think
this is probably more readable.

Hope this clears things up (a little, anyway...)
-- 
Rich "Roadie Rich" Lovely

Just because you CAN do something, doesn't necessarily mean you SHOULD.
In fact, more often than not, you probably SHOULDN'T.  Especially if I
suggested it.

10 re-discover BASIC
20 ???
30 PRINT "Profit"
40 GOTO 10
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] recursive generator

2010-03-07 Thread Chris Fuller

Here's a (more or less) useful example.  It generates the "nth triangular 
series" (my term), n=2 is the triangular numbers, n=3 the tetrahedral numbers 
(n has a correspondence to dimension).  Each series is the sum of the elements 
of the previous one.  They are also the columns of Pascal's Triangle.

Probably only "useful" if you enjoy playing around with math.

def triangular(n):
   if n == 0:
  while True:
 yield 1
   else:
  g = triangular(n-1)
  a = 0
  while True:
 a += g.next()
 yield a


I wouldn't use that to build Pascal's triangle, by the way (usually you want 
it rowwise).  Here's my code.

def pascal():
   r = 0
   l = [1]
   yield l

   while True:
  l  = [1] + [ l[i] + l[i+1] for i in range(r) ] + [1]
  r +=  1

  yield l

>>> g=pascal()
>>> for i in range(9):
...  print ' '.join(['%2d'%(i,) for i in g.next()])
... 
 1
 1  1
 1  2  1
 1  3  3  1
 1  4  6  4  1
 1  5 10 10  5  1
 1  6 15 20 15  6  1
 1  7 21 35 35 21  7  1
 1  8 28 56 70 56 28  8  1

Here are the triangular-ly generated numbers (I made them a square, just to 
illustrate an interesting property.)
>>> for i in range(7):
...  g=triangular(i)
...  print ' '.join(['%3d'%(j,) for j in [g.next() for k in range(7)]])
... 
  1   1   1   1   1   1   1
  1   2   3   4   5   6   7
  1   3   6  10  15  21  28
  1   4  10  20  35  56  84
  1   5  15  35  70 126 210
  1   6  21  56 126 252 462
  1   7  28  84 210 462 924


Cheers

On Sunday 07 March 2010, spir wrote:
> Hello,
> 
> Is it possible at all to have a recursive generator? I think at a iterator
>  for a recursive data structure (here, a trie). The following code does not
>  work: it only yields a single value. Like if child.__iter__() were never
>  called.
> 
> def __iter__(self):
> ''' Iteration on (key,value) pairs. '''
> print '*',
> if self.holdsEntry:
> yield (self.key,self.value)
> for child in self.children:
> print "<",
> child.__iter__()
> print ">",
> raise StopIteration
> 
> With the debug prints in code above, "for e in t: print e" outputs:
> 
> * ('', 0)
> < > < > < > < > < > < > < > < >
> 
> print len(t.children) # --> 9
> 
> Why is no child.__iter__() executed at all? I imagine this can be caused by
>  the special feature of a generator recording current execution point. (But
>  then, is it at all possible to have a call in a generator? Or does the
>  issue only appear whan the callee is a generator itself?) Else, the must
>  be an obvious error in my code.
> 
> 
> Denis
> 

___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


[Tutor] recursive generator

2010-03-07 Thread Hugo Arts
sorry, forgot to forward this to the list.

On Sun, Mar 7, 2010 at 1:58 PM, spir  wrote:
> Hello,
>
> Is it possible at all to have a recursive generator? I think at a iterator 
> for a recursive data structure (here, a trie). The following code does not 
> work: it only yields a single value. Like if child.__iter__() were never 
> called.
>
>    def __iter__(self):
>        ''' Iteration on (key,value) pairs. '''
>        print '*',
>        if self.holdsEntry:
>            yield (self.key,self.value)
>        for child in self.children:
>            print "<",
>            child.__iter__()
>            print ">",
>        raise StopIteration
>
> With the debug prints in code above, "for e in t: print e" outputs:
>
> * ('', 0)
> < > < > < > < > < > < > < > < >
>
> print len(t.children) # --> 9
>
> Why is no child.__iter__() executed at all? I imagine this can be caused by 
> the special feature of a generator recording current execution point. (But 
> then, is it at all possible to have a call in a generator? Or does the issue 
> only appear whan the callee is a generator itself?) Else, the must be an 
> obvious error in my code.
>
>
> Denis

remember that child.__iter__ returns a generator object. Merely
calling the function won't do anything. To actually get elements from
a generator object, you need to call next() on the returned iterator,
or iterate over it in another way (e.g. a for loop). I would write
this method more or less like so:

from itertools import chain

def __iter__(self):
   this_entry = [(self.key, self.value)] is self.holds_entry else []
   return chain(this_entry, *[iter(c) for c in self.children])

(Warning! untested! I'm pretty sure chain will work like this, but you
might have to do a little tweaking)
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] recursive generator

2010-03-07 Thread Steven D'Aprano
On Mon, 8 Mar 2010 01:49:21 am Stefan Behnel wrote:
> Steven D'Aprano, 07.03.2010 14:27:
> > __iter__ should be an ordinary function, not a generator. Something
> > like this should work:
[...]
> That's just an unnecessarily redundant variation on the above. It's
> perfectly ok if __iter__() is a generator method.

So it is. Thank you for the correction.



-- 
Steven D'Aprano
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] recursive generator

2010-03-07 Thread Stefan Behnel

Steven D'Aprano, 07.03.2010 14:27:

On Sun, 7 Mar 2010 11:58:05 pm spir wrote:

 def __iter__(self):
 ''' Iteration on (key,value) pairs. '''
 print '*',
 if self.holdsEntry:
 yield (self.key,self.value)
 for child in self.children:
 print "<",
 child.__iter__()
 print ">",
 raise StopIteration



__iter__ should be an ordinary function, not a generator. Something like
this should work:

# Untested.
 def __iter__(self):
 ''' Iteration on (key,value) pairs. '''
 def inner():
 print '*',  # Side effects bad...
 if self.holdsEntry:
 yield (self.key,self.value)
 for child in self.children:
 print "<",
 child.__iter__()
 print ">",
 raise StopIteration
 return inner()


That's just an unnecessarily redundant variation on the above. It's 
perfectly ok if __iter__() is a generator method.


Stefan

___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] recursive generator

2010-03-07 Thread Steven D'Aprano
On Sun, 7 Mar 2010 11:58:05 pm spir wrote:
> Hello,
>
> Is it possible at all to have a recursive generator? 


Yes.


>>> def recursive_generator(n):
... if n == 0:
... return
... yield n
... for i in recursive_generator(n-1):
... yield i
...
>>> it = recursive_generator(5)
>>> it.next()
5
>>> it.next()
4
>>> it.next()
3
>>> it.next()
2
>>> it.next()
1
>>> it.next()
Traceback (most recent call last):
  File "", line 1, in 
StopIteration


> I think at a 
> iterator for a recursive data structure (here, a trie). The following
> code does not work: it only yields a single value. Like if
> child.__iter__() were never called.
>
> def __iter__(self):
> ''' Iteration on (key,value) pairs. '''
> print '*',
> if self.holdsEntry:
> yield (self.key,self.value)
> for child in self.children:
> print "<",
> child.__iter__()
> print ">",
> raise StopIteration


__iter__ should be an ordinary function, not a generator. Something like 
this should work:

# Untested.
def __iter__(self):
''' Iteration on (key,value) pairs. '''
def inner():
print '*',  # Side effects bad...
if self.holdsEntry:
yield (self.key,self.value)
for child in self.children:
print "<",
child.__iter__()
print ">",
raise StopIteration
return inner()


This means that your class won't *itself* be an iterator, but calling 
iter() on it will return a generator object, which of course is an 
iterator.

If you want to make your class an iterator itself, then you need to 
follow the iterator protocol. __iter__ must return the instance itself, 
and next must return (not yield) the next iteration.

class MyIterator(object):
def __init__(self, n):
self.value = n
def next(self):
if self.value == 0:
raise StopIteration
self.value //= 2
return self.value
def __iter__(self):
return self

See the discussion in the docs about the iterator protocol:

http://docs.python.org/library/stdtypes.html#iterator-types



> Why is no child.__iter__() executed at all? I imagine this can be
> caused by the special feature of a generator recording current
> execution point.

That's exactly what generators do: when they reach a yield, execution is 
halted, but the state of the generator is remembered. Then when you 
call the next() method, execution resumes.



> (But then, is it at all possible to have a call in a 
> generator? Or does the issue only appear whan the callee is a 
> generator itself?) Else, the must be an obvious error in my code.


I don't understand what you mean.


-- 
Steven D'Aprano
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


[Tutor] recursive generator

2010-03-07 Thread spir
Hello,

Is it possible at all to have a recursive generator? I think at a iterator for 
a recursive data structure (here, a trie). The following code does not work: it 
only yields a single value. Like if child.__iter__() were never called.

def __iter__(self):
''' Iteration on (key,value) pairs. '''
print '*',
if self.holdsEntry:
yield (self.key,self.value)
for child in self.children:
print "<",
child.__iter__()
print ">",
raise StopIteration

With the debug prints in code above, "for e in t: print e" outputs:

* ('', 0)
< > < > < > < > < > < > < > < >

print len(t.children) # --> 9

Why is no child.__iter__() executed at all? I imagine this can be caused by the 
special feature of a generator recording current execution point. (But then, is 
it at all possible to have a call in a generator? Or does the issue only appear 
whan the callee is a generator itself?) Else, the must be an obvious error in 
my code.


Denis
-- 


la vita e estrany

spir.wikidot.com

___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor