Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to detect the last element in a for loop

0 views
Skip to first unread message

Tom Verbeure

unread,
Jul 27, 2002, 4:21:44 PM7/27/02
to

Hello All,

I often have the case where I need to loop through a bunch of elements, but
do something special on for the last line of code.

Currently, I can solve it this way:

first = 1
for port in self.portDefsList:
if first == 0:
string += ", "
else:
first = 0
string += str(port)
pass

This works fine, but it introduces an extra variable. Is there a cleaner
way to do this?

Thanks,
Tom


Alex Martelli

unread,
Jul 27, 2002, 4:42:03 PM7/27/02
to
Tom Verbeure wrote:

>
> Hello All,
>
> I often have the case where I need to loop through a bunch of elements,
> but do something special on for the last line of code.

That's not what you're doing below:

> Currently, I can solve it this way:
>
> first = 1
> for port in self.portDefsList:
> if first == 0:
> string += ", "
> else:
> first = 0
> string += str(port)
> pass

Here, you're doing something special (avoiding the += ", ") the FIRST
time. The pass is unconditional and adds absolutely nothing -- just
take it away.

Anyway, this specific need is met to perfection by:
string += ", ".join([ str(port) for port in self.portDefsList ])

joiner.join(sequenceOfStrings) is a powerful way to meet many
problems of the "I must do something each time except the first/last"
variety -- all those problems which boil down to joining strings
with some joiner.

As a nice plus, it's also *WAY, WAY* faster. A loop with += takes
O(N squared) time, joiner.join takes O(N) time, where N is the
size of the input. Don't use loops of += on strings except in
really short, trivial cases -- prepare a list and join it at the
end, or use a cStringIO instance x and call x.write, etc, etc.


For more general needs, you can sometimes use slices to good effect.
I.e., instead of:

first = 1
for item in somelist:
if first:
processfirst(item)
first = 0
else:
processnormal(item)

you can do:

processfirst(somelist[0])
for item in somelist[1:]:
processnormal(item)

and if the special case needs to be the LAST one:

for item in somelist[:-1]:
processnormal(item)
processlast(somelist[-1])


This does assume that you have a sequence, not just an iterator, and can
thus slice off the first or last items easily. It's trickier but
not difficult to do special-on-first on an iterator:

processfirst(iterator.next())
for item in iterator:
processnormal(item)

the .next call returns and "consumes" the first item, so the following
for starts from the second one, and all is sweetness and light.


Detecting the LAST item for special processing is trickier still if
what you DO have is an iterator. You basically have to "stagger"
the output, e.g.:

saved = iterator.next()
for item in iterator:
processnormal(saved)
saved = item
processlast(saved)

There's no way out here from the "extra variable" -- you may hide
it somewhere, but SOMEwhere it does have to be.


Alex

Terry Reedy

unread,
Jul 27, 2002, 4:45:30 PM7/27/02
to

"Tom Verbeure" <tom.ve...@verizon.no.sp.am.net> wrote in message
news:shD09.10086$9U4....@nwrddc01.gnilink.net...

>
> Hello All,
>
> I often have the case where I need to loop through a bunch of
elements, but
> do something special on for the last line of code.

> first = 1


> for port in self.portDefsList:
> if first == 0:
> string += ", "
> else:
> first = 0
> string += str(port)
> pass

You actually special case the first item. However, you really want
the string join method:

result = ', '.join(map(str, self.portDefsList))

Terry J. Reedy

Tom Verbeure

unread,
Jul 27, 2002, 5:33:27 PM7/27/02
to

>>
>> Hello All,
>>
>> I often have the case where I need to loop through a bunch of elements,
>> but do something special on for the last line of code.
>
> That's not what you're doing below:
>
>> Currently, I can solve it this way:
>>
>> first = 1
>> for port in self.portDefsList:
>> if first == 0:
>> string += ", "
>> else:
>> first = 0
>> string += str(port)
>> pass
>
> Here, you're doing something special (avoiding the += ", ") the FIRST
> time. The pass is unconditional and adds absolutely nothing -- just
> take it away.

Duh. I copied the wrong piece of code. :-]

> Anyway, this specific need is met to perfection by:
> string += ", ".join([ str(port) for port in self.portDefsList ])

Interesting.
My focus wasn't really on joining strings, but I didn't know this either.

> This does assume that you have a sequence, not just an iterator, and can
> thus slice off the first or last items easily. It's trickier but
> not difficult to do special-on-first on an iterator:
>
> processfirst(iterator.next())
> for item in iterator:
> processnormal(item)
>
> the .next call returns and "consumes" the first item, so the following
> for starts from the second one, and all is sweetness and light.

I am not familiar with iterators as objects in Python. I understand that

for a in myList:
...

will result in an implicit iterator on myList. How would you create an
explicit iterator object of myList?

> Detecting the LAST item for special processing is trickier still if
> what you DO have is an iterator. You basically have to "stagger"
> the output, e.g.:
>
> saved = iterator.next()
> for item in iterator:
> processnormal(saved)
> saved = item
> processlast(saved)

Given that you have an explicit iterator, wouldn't it be trivial to add an
'end()' method to this iterator to indicate the end of the sequence (just
like C++ iterators) ?

This would result in:

for item in iterator:
if iterator.next().end():
do_something_special

do_something_for_all

Thanks,
Tom

Fredrik Lundh

unread,
Jul 27, 2002, 6:02:52 PM7/27/02
to
Tom Verbeure wrote:

> I am not familiar with iterators as objects in Python. I understand that
>
> for a in myList:
> ...
>
> will result in an implicit iterator on myList. How would you create an
> explicit iterator object of myList?

the built-in iter() function will do this for you:

>>> L = [1, 2, 3, 4, 5]
>>> I = iter(L)
>>> L
[1, 2, 3, 4, 5]
>>> I
<iterator object at 0x007D9728>
>>> I.next()
1
>>> for i in I:
... print i
...
2
3
4
5
>>> I.next()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration

</F>


Bryan Olson

unread,
Jul 28, 2002, 2:00:43 AM7/28/02
to
Tom Verbeure wrote:
[...]

> I am not familiar with iterators as objects in Python. I understand that
>
> for a in myList:
> ...
>
> will result in an implicit iterator on myList.

For a simple solution, how about:

for a in myList[:-1]:
do_stuff(a)
special_stuff(myList[-1])


[...]


> Given that you have an explicit iterator, wouldn't it be trivial to
add an
> 'end()' method to this iterator to indicate the end of the sequence
(just
> like C++ iterators) ?
>
> This would result in:
>
> for item in iterator:
> if iterator.next().end():
> do_something_special
>
> do_something_for_all

I think you've misunderstood the end() value in C++ STL iterators. It
is not the last item, but one past the last item. The iterator can take
the end value, but dereferencing the end value is illegal.


--Bryan

Tom Verbeure

unread,
Jul 28, 2002, 9:16:48 AM7/28/02
to

> For a simple solution, how about:
>
> for a in myList[:-1]:
> do_stuff(a)
> special_stuff(myList[-1])

No, I still want to do 'do_stuff' for the last element also. This may be,
say, 10 lines of code. Too much to duplicate it outside the loop, not
enough for a separate function...

> > Given that you have an explicit iterator, wouldn't it be trivial to
> add an
> > 'end()' method to this iterator to indicate the end of the sequence
> (just
> > like C++ iterators) ?
> >
> > This would result in:
> >
> > for item in iterator:
> > if iterator.next().end():
> > do_something_special
> >
> > do_something_for_all
>
> I think you've misunderstood the end() value in C++ STL iterators. It
> is not the last item, but one past the last item. The iterator can take
> the end value, but dereferencing the end value is illegal.

That would be the case if I would check for iterator.end(), but I check for
iterator.next().end() !

Tom

François Pinard

unread,
Jul 28, 2002, 10:11:18 AM7/28/02
to
[Tom Verbeure]

> > For a simple solution, how about:
> >
> > for a in myList[:-1]:
> > do_stuff(a)
> > special_stuff(myList[-1])

> No, I still want to do 'do_stuff' for the last element also. This may be,
> say, 10 lines of code. Too much to duplicate it outside the loop, not
> enough for a separate function...

I did not follow this thread, I may be missing something, but why not:

for a in myList:
do_stuff(a)
special_stuff(a)

taking advantage on the fact that `a' keeps the last value it received?

--
François Pinard http://www.iro.umontreal.ca/~pinard

Fredrik Lundh

unread,
Jul 28, 2002, 11:02:16 AM7/28/02
to
Tom Verbeure wrote:

> > For a simple solution, how about:
> >
> > for a in myList[:-1]:
> > do_stuff(a)
> > special_stuff(myList[-1])
>
> No, I still want to do 'do_stuff' for the last element also. This may be,
> say, 10 lines of code. Too much to duplicate it outside the loop, not
> enough for a separate function...

so why not just change the for loop?

for a in myList:
do_stuff(a)

special_stuff(myList[-1])

for extra style points, you can use the else statement to
make sure the special stuff isn't done if you have to break
out of the loop:

for a in myList:
do_stuff(a)

else:
special_stuff(a)

> That would be the case if I would check for iterator.end(), but I check for
> iterator.next().end() !

as the code in my earlier post tried to tell you, iterator.next()
returns the next item from the sequence, or raises an exception.

to implement your proposal, *all* objects need to implement an
end method, which should return true if they are the last object
in a container. since objects can be shared (the containers only
hold references), and objects don't know or care about what
containers they are in today, that's pretty much means that
we have to start over from scratch...

</F>

<!-- (the eff-bot guide to) the python standard library:
http://www.pythonware.com/people/fredrik/librarybook.htm
-->

Roy Smith

unread,
Jul 28, 2002, 11:21:17 AM7/28/02
to
Tom Verbeure <tom.ve...@verizon.no.sp.am.net> wrote:
> say, 10 lines of code. Too much to duplicate it outside the loop, not
> enough for a separate function...

Why is 10 lines of code not enough for a separate function? I use
functions to group code into conceptual blocks that can be thought about
and understood as a unit. Sometimes that block is 100 lines of code,
sometimes it's 3 or 4 lines. I've even written one-line functions.
Whatever makes sense to factor out as an atomic unit. Line count has
little to do with it.

I'm not quite as rabid about it as the XP'er are with "Refactor
Mercilessly", but (like so much with XP) the basic idea has merit if you
don't try to be too extreme about it :-)

John Hunter

unread,
Jul 28, 2002, 11:49:56 AM7/28/02
to
>>>>> "Tom" == Tom Verbeure <tom.ve...@verizon.no.sp.am.net> writes:

Tom> Hello All,

Tom> I often have the case where I need to loop through a bunch of
Tom> elements, but do something special on for the last line of
Tom> code.


What about the good, old fashioned index? To my eyes, it is very
readable

N = len(seq):
for i in range(N):
do_something(seq[i])
if i==someVal:
do_something_else(seq[i])

It's not really that god awful is it?

Also, for the example you posted, you can use string.join....

import string
seq = range(10)
s = string.join(map(str, seq), ', ')

Cheers,
John Hunter

Tom Verbeure

unread,
Jul 28, 2002, 1:04:27 PM7/28/02
to

> I did not follow this thread, I may be missing something, but why not:
>
> for a in myList:
> do_stuff(a)
> special_stuff(a)
>
> taking advantage on the fact that `a' keeps the last value it received?

Then what about this?

for a in myList:
if last_one:
special_stuff
do_stuff
Tom

Tom Verbeure

unread,
Jul 28, 2002, 1:23:57 PM7/28/02
to
Roy Smith wrote:

> Tom Verbeure <tom.ve...@verizon.no.sp.am.net> wrote:
>> say, 10 lines of code. Too much to duplicate it outside the loop, not
>> enough for a separate function...
>
> Why is 10 lines of code not enough for a separate function? I use
> functions to group code into conceptual blocks that can be thought about
> and understood as a unit. Sometimes that block is 100 lines of code,
> sometimes it's 3 or 4 lines. I've even written one-line functions.
> Whatever makes sense to factor out as an atomic unit. Line count has
> little to do with it.

Ha! Now we are talking style! :-)
Yes, that's, of course, an option also, but we are moving away from the
original question and I feel guilty already wasting this forums' reader
time with my trival question...

My main reason not to use function in this case, is that it may result in
moving meaning full code too far away from where the action is. However,
after reading some things on the web, I can solve this by added nested
functions:

def BigFunction:

< lots of code >

def DefaultForLoopCode():
pass

for a in myList[:-1]:

DefaultForLoopCode()
DoExtra()
DefaultForLoopCode()

I think this will solve my problem, while still keeping everything
relatively clean.

Thanks,
Tom


François Pinard

unread,
Jul 28, 2002, 1:48:20 PM7/28/02
to
[Tom Verbeure]

> Then what about this?

> for a in myList:
> if last_one:
> special_stuff
> do_stuff

Eh! Some require a bit more work than others :-).

Andrae Muys

unread,
Jul 28, 2002, 8:18:37 PM7/28/02
to
Tom Verbeure <tom.ve...@verizon.no.sp.am.net> wrote in message news:<49S09.13011$9U4....@nwrddc01.gnilink.net>...

> > For a simple solution, how about:
> >
> > for a in myList[:-1]:
> > do_stuff(a)
> > special_stuff(myList[-1])
>
> No, I still want to do 'do_stuff' for the last element also. This may be,
> say, 10 lines of code. Too much to duplicate it outside the loop, not
> enough for a separate function...
>

Now this comment I find strange, as personally I consider 10 lines of
code about the perfect length for a function. If your functions are
consise they become self documenting. As an added bonus, similar
functions appearing in different sections (often unrelated) of code,
can often suggest powerful abstractions that can make your code more
flexible and more maintainable. I personally prefer a function to do
one thing. If I find myself needing more then 15-20 lines to describe
'one thing' I stop and ask myself why I'm having so much trouble
describing 'one thing' consisely. Is the function actually doing
multiple things? Do I really understand what the functions trying to
do? Am I trying to use the wrong programmign-paradigm to describe
'one thing'? Maybe the function's in the wrong place, and working too
hard to obtain the data it needs? Whatever the problem, I find a
function of >20 lines a symptom that there is something wrong.

5-10 lines + error handling.

that's-my-general-target-ly yours

Andrae Muys

Christopher A. Craig

unread,
Jul 29, 2002, 12:41:10 AM7/29/02
to
Tom Verbeure <tom.ve...@verizon.no.sp.am.net> writes:

> Then what about this?
>
> for a in myList:
> if last_one:
> special_stuff
> do_stuff
> Tom

Okay, not as simple, but it should work

for a, i in zip(myList, xrange(len(myList))):
if i==(len(myList)-1):
special_stuff
do_stuff

--
Christopher A. Craig <list-...@ccraig.org>
"Imagination is more important than knowledge" Albert Einstein

Christopher A. Craig

unread,
Jul 29, 2002, 12:52:06 AM7/29/02
to
Tom Verbeure <tom.ve...@verizon.no.sp.am.net> writes:

> Given that you have an explicit iterator, wouldn't it be trivial to add an
> 'end()' method to this iterator to indicate the end of the sequence (just
> like C++ iterators) ?
>
> This would result in:
>
> for item in iterator:
> if iterator.next().end():
> do_something_special
>
> do_something_for_all


I do not think it means what you think it means:

>>> iterator = iter([0,1,2,3])
>>> for i in iterator:
... if iterator.next(): pass
... print i
0
2

As you see, iterator.next() moves iterator to the next item, which
means that even if you had a .end() method (which you don't), you
couldn't use it like this.

One way you could do this (another, better, way to do it is in another
post, but only works if you know the length at the beginning) is to
always track the next item:

next = iterator.next()
while next:
this=next
try:
next=iterator.next()
except StopIteration:
next=False
do_something_special
do_something_for_all


--
Christopher A. Craig <list-...@ccraig.org>

"640K ought to be enough for anybody." Bill Gates

Bryan Olson

unread,
Jul 29, 2002, 10:29:43 AM7/29/02
to
Tom Verbeure wrote:
[...]

>> > Given that you have an explicit iterator, wouldn't it be trivial to
>>add an
>> > 'end()' method to this iterator to indicate the end of the sequence
>>(just
>> > like C++ iterators) ?
>> >
>> > This would result in:
>> >
>> > for item in iterator:
>> > if iterator.next().end():
>> > do_something_special
>> >
>> > do_something_for_all
>>
>>I think you've misunderstood the end() value in C++ STL iterators. It
>>is not the last item, but one past the last item. The iterator can take
>>the end value, but dereferencing the end value is illegal.
>
>
> That would be the case if I would check for iterator.end(), but I
check for
> iterator.next().end() !

For a given C++ STL iterator, end() will return the *same* value no
matter how many times you advance the iterator. It looks like you
wanted:

...
if iterator.next() == iterator.end():
do_something_special


--Bryan


Bill Dandreta

unread,
Jul 30, 2002, 2:22:30 PM7/30/02
to
Hi Alex,

On Sat, 27 Jul 2002 20:42:03 GMT, Alex Martelli <al...@aleax.it>
wrote:

>As a nice plus, it's also *WAY, WAY* faster. A loop with += takes
>O(N squared) time, joiner.join takes O(N) time, where N is the
>size of the input. Don't use loops of += on strings except in
>really short, trivial cases -- prepare a list and join it at the
>end, or use a cStringIO instance x and call x.write, etc, etc.

I am using the following code structure in some very long (10's of
thousands of iterations) loops.

Is there a more efficient way that doesn't use +=?

Would s5 = '%s,"%s"' % (s5,x[1]) be better?

t = ()
s5 = ''
for i in range(0,5):
q = breaks[brk][i]
x = mkup(cost*q,list*q)
if x[0]: t+=(x[0]/q,)
else: t+=(0,)
if x[1]: s5+=',"'+x[1]+'"'
else: s5+=',"-"'
t += (0,0)
s5 += ',"-","-"'

Bill

Thomas Jensen

unread,
Jul 30, 2002, 3:04:19 PM7/30/02
to
Bill Dandreta wrote:
> Hi Alex,
>
> On Sat, 27 Jul 2002 20:42:03 GMT, Alex Martelli <al...@aleax.it>
> wrote:
>
>>As a nice plus, it's also *WAY, WAY* faster. A loop with += takes
>>O(N squared) time, joiner.join takes O(N) time, where N is the

[snip]

> I am using the following code structure in some very long (10's of
> thousands of iterations) loops.
>
> Is there a more efficient way that doesn't use +=?
>
> Would s5 = '%s,"%s"' % (s5,x[1]) be better?

Probably not (it would still have the O(N squared) charactaristics).
Instead try:

s5_list = []
for ...:
s5_list.append(',"%s"' % x[1])

s5 = ''.join(s5_list)

--
Best Regards
Thomas Jensen
(remove underscore in email address to mail me)

Yigal Duppen

unread,
Jul 30, 2002, 3:12:32 PM7/30/02
to
> I am using the following code structure in some very long (10's of
> thousands of iterations) loops.
> Is there a more efficient way that doesn't use +=?

> Would s5 = '%s,"%s"' % (s5,x[1]) be better?
>
> t = ()
> s5 = ''
> for i in range(0,5):
> q = breaks[brk][i]
> x = mkup(cost*q,list*q)
> if x[0]: t+=(x[0]/q,)
> else: t+=(0,)
> if x[1]: s5+=',"'+x[1]+'"'
> else: s5+=',"-"'
> t += (0,0)
> s5 += ',"-","-"'

Without exactly understanding what it does, here are some tips:

1) make t a list; this allows you to use 'append'. This is much faster since
append works on an existing list. <tuple> += <tuple> creates a new tuple
every time. <list> += <list> is quite efficient as well

2) make s a list as well, append all the strings you need, and join them in
the end.


Something like this should be faster:

t_list = []
s5_list = []
for i in range(0, 5):
q = breaks[break][i]
x = mkup(cost * q, list * q)
if x[0]:
t_list.append(x[0]/q)
else:
t_list.append(0)

if x[1]:
s5_list += [ ',"' , x[1] , '"']
else:
s5_list.append(',"-"')
t_list += (0, 0)
s5_list.append(',"-","-"')

# and convert both lists to the desired types
t = tuple(t_list)
s5 = "".join(s5_list)


No clue how much faster; that's what profile is for.

YDD
--
.sigmentation fault

Andreas Kostyrka

unread,
Jul 30, 2002, 4:41:00 PM7/30/02
to
Am Son, 2002-07-28 um 08.00 schrieb Bryan Olson:
> Tom Verbeure wrote:
> [...]
> > I am not familiar with iterators as objects in Python. I understand that
> >
> > for a in myList:
> > ...
> >
> > will result in an implicit iterator on myList.
>
> For a simple solution, how about:
>
> for a in myList[:-1]:
> do_stuff(a)
> special_stuff(myList[-1])
Breaks on myList=[]
Better:

for a in myList[:-1]:
do_stuff(a)
for a in myList[-1:]:
special_stuff(a)

That works, because slicing an empty list always returns an empty list,
while accessing an nonexistant element throws an exception. :)

Andreas


Andreas Kostyrka

unread,
Jul 30, 2002, 4:52:01 PM7/30/02
to
Am Son, 2002-07-28 um 15.16 schrieb Tom Verbeure:
>
>
> > For a simple solution, how about:
> >
> > for a in myList[:-1]:
> > do_stuff(a)
> > special_stuff(myList[-1])
>
> No, I still want to do 'do_stuff' for the last element also. This may be,
> say, 10 lines of code. Too much to duplicate it outside the loop, not
> enough for a separate function...
Then do this:
for a in myList:
do_stuff(a):
if myList:
special_stuff(myList[-1])

Andreas


Duncan Booth

unread,
Jul 31, 2002, 5:06:02 AM7/31/02
to
Yigal Duppen <ydu...@xs4all.nl> wrote in
news:3d46e4d4$0$94903$e4fe...@dreader3.news.xs4all.nl:

> Something like this should be faster:
>
> t_list = []
> s5_list = []
> for i in range(0, 5):
> q = breaks[break][i]
> x = mkup(cost * q, list * q)
> if x[0]:
> t_list.append(x[0]/q)
> else:
> t_list.append(0)
>
> if x[1]:
> s5_list += [ ',"' , x[1] , '"']
> else:
> s5_list.append(',"-"')
> t_list += (0, 0)
> s5_list.append(',"-","-"')
>
> # and convert both lists to the desired types
> t = tuple(t_list)
> s5 = "".join(s5_list)
>
>
> No clue how much faster; that's what profile is for.

A few other suggestions that might or might not help:

I'm assuming the real code is inside a function, if not put it there at
once (that will speed it up a lot).

If mkup is simply returning two values, then use tuple unpacking to assign
the results directly into two variables instead of indexing. This allows
you to give the results meaningful names.

If the first result of mkup is always numeric, then you can collapse the if
statement down to a single append.

Optimise out the references to the append method from inside the loop.

Avoid using range in a for loop. 'i' isn't needed here at all.

All of which together (apart from the function) give you something like:

t_list, s5_list = [], []
t_append, s_append = t_list.append, s5_list.append

for q in breaks[break]:
numeric, stringval = mkup(cost * q, list * q)
t_append(numeric and numeric/q)
s_append(',"%s"' % stringval or "-")

t_list += (0, 0)
s_append(',"-","-"')

# and convert both lists to the desired types
t = tuple(t_list)
s5 = "".join(s5_list)


--
Duncan Booth dun...@rcp.co.uk
int month(char *p){return(124864/((p[0]+p[1]-p[2]&0x1f)+1)%12)["\5\x8\3"
"\6\7\xb\1\x9\xa\2\0\4"];} // Who said my code was obscure?

0 new messages