Last iteration?

P

Paul Rubin

Raymond Hettinger said:
We need a C-speed verion of the lambda function, something like a K
combinator that consumes arguments and emits constants.

Some discussion of this is at <http://bugs.python.org/issue1673203>.
I had suggested implementing K through an optional second arg for a
proposed identity function but everyone hated that. I'm amused. I
had suggested it because I thought that it would be easier than
getting two separate functions accepted.
 
H

Hendrik van Rooyen

Raymond Hettinger said:
More straight-forward version:

def lastdetecter(iterable):
t, lookahead = tee(iterable)
lookahead.next()
return izip(chain(imap(itemgetter(0), izip(repeat(False),
lookahead)), repeat(True)), t)

If this is what you call straightforward - heaven forfend
that I ever clap my orbs on something you call convoluted!

:)

Faced with this problem, I would probably have used
enumerate with a look ahead and the IndexError would
have told me I am at the end...

- Hendrik
 
P

Paul Hankin

[Paul Hankin]
def lastdetector(iterable):
t, u = tee(iterable)
return izip(chain(imap(lambda x: False, islice(u, 1, None)),
[True]), t)

Sweet! Nice, clean piece of iterator algebra.

We need a C-speed verion of the lambda function, something like a K
combinator that consumes arguments and emits constants.

Perhaps, but I think you'll need a better use-case than this :)

Actually, would a c-version be much faster?
 
M

Michael J. Fromberger

Raymond Hettinger said:
[Diez B. Roggisch]
[Peter Otten]
You show signs of a severe case of morbus itertools.
I, too, am affected and have not yet fully recovered...

Maybe you guys were secretly yearning for a magical last element
detector used like this: [...]


Although there have already been some nice solutions to this problem,
but I'd like to propose another, which mildly abuses some of the newer
features of Python It is perhaps not as concise as the previous
solutions, nor do I claim it's better; but I thought I'd share it as an
alternative approach.

Before I affront you with implementation details, here's an example:

| from __future__ import with_statement

| with last_of(enumerate(file('/etc/passwd', 'rU'))) as fp:
| for pos, line in fp:
| if fp.marked():
| print "Last line, #%d = %s" % (pos + 1, line.strip())

In short, last_of comprises a trivial context manager that knows how to
iterate over its input, and can also indicate that certain elements are
"marked". In this case, only the last element is marked.

We could also make the truth value of the context manager indicate the
marking, as illustrated here:

| with last_of("alphabet soup") as final:
| for c in final:
| if final:
| print "Last character: %s" % c

This is bit artificial, perhaps, but effective enough. Of course, there
is really no reason you have to use "with," since we don't really care
what happens when the object goes out of scope: You could just as
easily write:

| end = last_of(xrange(25))
| for x in end:
| if end:
| print "Last element: %s" % x

But you could also handle nested context, using "with". Happily, the
machinery to do all this is both simple and easily generalized to other
sorts of "marking" tasks. For example, we could just as well do
something special with all the elements that are accepted by a predicate
function, e.g.,

| def isinteger(obj):
| return isinstance(obj, (int, long))

| with matching(["a", 1, "b", 2, "c"], isinteger) as m:
| for elt in m:
| if m.marked():
| print '#%s' % elt,
| else:
| print '(%s)' % elt,
|
| print

Now that you've seen the examples, here is an implementation. The
"marker" class is an abstract base that does most of the work, with the
"last_of" and "matching" examples implemented as subclasses:

| class marker (object):
| """Generate a trivial context manager that flags certain elements
| in a sequence or iterable.
|
| Usage sample:
| with marker(ITERABLE) as foo:
| for elt in foo:
| if foo.marked():
| print 'this is a marked element'
| else:
| print 'this is an unmarked element'
|
| Subclass overrides:
| .next() -- return the next unconsumed element from the input.
| .marked() -- return True iff the last element returned is marked.
|
| By default, no elements are marked.
| """
| def __init__(self, seq):
| self._seq = iter(seq)
| try:
| self._fst = self._seq.next()
| except StopIteration:
| self.next = self._empty
|
| def _empty(self):
| raise StopIteration
|
| def _iter(self):
| while True:
| yield self.next()
|
| def next(self):
| out = self._fst
| try:
| self._fst = self._seq.next()
| except StopIteration:
| self.next = self._empty
|
| return out
|
| def marked(self):
| return False
|
| def __iter__(self):
| return iter(self._iter())
|
| def __nonzero__(self):
| return self.marked()
|
| def __enter__(self):
| return self
|
| def __exit__(self, *args):
| pass

A bit verbose, but uncomplicated apart from the subtlety in handling the
end case. Here's last_of, implemented as a subclass:

| class last_of (marker):
| def __init__(self, seq):
| super(last_of, self).__init__(seq)
| self._end = False
|
| def next(self):
| out = super(last_of, self).next()
| if self.next == self._empty:
| self._end = True
|
| return out
|
| def marked(self):
| return self._end

And finally, matching:

| class matching (marker):
| def __init__(self, seq, func):
| super(matching, self).__init__(seq)
| self._func = func
| self._mark = False
|
| def next(self):
| out = super(matching, self).next()
| self._mark = self._func(out)
| return out
|
| def marked(self):
| return self._mark

Generally speaking, you should only have to override .next() and
..marked() to make a useful subclass of marker -- and possibly also
__init__ if you need some additional setup.

Cheers,
-M
 
G

Gabriel Genellina

En Fri, 19 Oct 2007 19:12:49 -0300, Michael J. Fromberger
Before I affront you with implementation details, here's an example:

| from __future__ import with_statement

| with last_of(enumerate(file('/etc/passwd', 'rU'))) as fp:
| for pos, line in fp:
| if fp.marked():
| print "Last line, #%d = %s" % (pos + 1, line.strip())

In short, last_of comprises a trivial context manager that knows how to
iterate over its input, and can also indicate that certain elements are
"marked". In this case, only the last element is marked.

The name is very unfortunate. I'd expect that last_of(something) would
return its last element, not an iterator.
Even with a different name, I don't like that marked() (a method of the
iterator) should be related to the current element being iterated.
We could also make the truth value of the context manager indicate the
marking, as illustrated here:

| with last_of("alphabet soup") as final:
| for c in final:
| if final:
| print "Last character: %s" % c

This is bit artificial, perhaps, but effective enough. Of course, there

Again, why should the trueness of final be related to the current element
being iterated?
is really no reason you have to use "with," since we don't really care
what happens when the object goes out of scope: You could just as
easily write:

| end = last_of(xrange(25))
| for x in end:
| if end:
| print "Last element: %s" % x

Again, why the truth value of "end" is related to the current "x" element?
But you could also handle nested context, using "with". Happily, the
machinery to do all this is both simple and easily generalized to other
sorts of "marking" tasks. For example, we could just as well do
something special with all the elements that are accepted by a predicate
function, e.g.,

| def isinteger(obj):
| return isinstance(obj, (int, long))

| with matching(["a", 1, "b", 2, "c"], isinteger) as m:
| for elt in m:
| if m.marked():
| print '#%s' % elt,
| else:
| print '(%s)' % elt,
|
| print

I think you are abusing context managers *a*lot*!
Even accepting such evil thing as matching(...), the above code could be
equally written as:

m = matching(...)
for elt in m:
...

Anyway, a simple generator that yields (elt, function(elt)) would be
enough...
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top