4

How can I create a for loop with a counter? I have a list, and I want to read an element after each n elements. I'd initially done this

for i in enumerate(n):
    print(i)

But as expected it prints every element instead of every nth element, what would be the Python way of solving this?

4
  • 3
    for i, item in enumerate(n):. i is your counter. Commented Jun 10, 2018 at 18:20
  • Use range(0,len(your list), n) for instance. Commented Jun 10, 2018 at 18:20
  • enumerate() is basically used to iterate over items of a list as well as to have an access to its index. Commented Jun 10, 2018 at 18:22
  • I'm not sure if you want new index numbering after adding steps. Then yes, you can combine slicing and enumerating, e.g.: for index, value in enumerate(n[::nth]): print(index, value) (but it will cost memory, because it's a copy) Commented Jun 10, 2018 at 18:39

4 Answers 4

2

I am not sure wich kind of value is n but usually there are this ways: (for me, n is a list)

for index, item in enumerate(n):
   if index % nth == 0: # If the output is not like you want, try doing (index + 1), or enumerate(n, start=1).
       print(item)

Other way could be:

for index in range(0, len(n), nth): # Only work with sequences
   print(n[index]) # If the output is not like you want, try doing n[index + 1]

Or:

for item in n[::nth]: # Low perfomance and hight memory consumption warning!! Only work with sequences
    print(item)

Even you can combine the first one with the last one:

for i, item in list(enumerate(n))[::nth]: # Huge low perfomance warning!!!
    print(item)

But I'm not sure if that has an advantage...

Also, if you are willing to make a function, you could do something similar to the enumerate function:

def myEnumerate(sequence, start=0, jump=1):
    n = start
    j = start // Or j = 0, that is your decision.
    for elem in sequence:
        if j % jump == 0:
            yield n, elem
            n += 1
        j += 1

for index, item in myEnumerate(n, jump=1):
    print(item)

Personally, I wouldn't do this last one. I'm not sure why but it's a feeling.

Perfomance test

n = 'a b c d e f g h i j k l m n ñ o p q r s t u v w x y z 1 2 3 4 5 6 7 8 9 ! " · $ % & / ( ) = ? ¿ Ç ç } { [ ] ; : _ ¨ ^ * ` + ´ - . , º ª \ /'.split(" ")
nth = 3    
def a():
    for i, item in enumerate(n):
       if i % nth == 0:
           item       
def b():
    for item in range(0, len(n), nth):
       n[item]           
def c():
    for item in n[::nth]:
        item    
def d():
    for i, item in list(enumerate(n))[::nth]:
       if i % nth == 0:
           item    
def enumerates(sequence, start=0, jump=1):
    n = start
    j = start
    for elem in sequence:
        if j % jump == 0:
            yield n, elem
            n += 1
        j += 1            
def e():
    for i, item in enumerates(n, jump= nth):
        item    
if __name__ == '__main__':
    import timeit
    print(timeit.timeit("a()", setup="from __main__ import a")) # 10.556324407152305
    print(timeit.timeit("b()", setup="from __main__ import b")) # 2.7166204783010137
    print(timeit.timeit("c()", setup="from __main__ import c")) # 1.0285353306076601
    print(timeit.timeit("d()", setup="from __main__ import d")) # 8.283859051918608
    print(timeit.timeit("e()", setup="from __main__ import e")) # 14.91601851631981

But if you are really looking for perfomance you should read @Martijn Pieters answer.

Sign up to request clarification or add additional context in comments.

15 Comments

Slicing gives you a copy of the input list object. Both the range() and the slice options only work on sequences, just iterables (which the enumerate() version would do).
@MartijnPieters I know that slicing gives you a copy, is that a problem? Also I know that my ideas doesn't work on everything. That is why I said usually work. But I can't give a better answer because I lack of the knowledge :)
Creating a copy could be a problem if the input is large, or if this part of the code is in a critical part of the code (copying takes time).
@MartijnPieters You have right with that. I didn't say it because I thought he would be doing something small, but I should add it to my answer. I'm editing it right now.
I don't know that rewriting enumerate is a great idea given that's a builtin, but it is nice to show how it might work.
|
2

Use the itertools.islice() object to limit iteration to every n-th object, this is at least twice as fast as any other proposed solution:

from itertools import islice

n = 5
for ob in islice(iterable, None, None, n):
    print(ob)

The above efficiently produces every 5th object, starting at the first:

>>> from itertools import islice
>>> from string import ascii_uppercase as iterable
>>> n = 5
>>> for ob in islice(iterable, None, None, n):
...     print(ob)
...
A
F
K
P
U
Z

Replace the first None with n - 1 if you want to skip to the nth object as the first to use:

>>> for ob in islice(iterable, n - 1, None, n):
...     print(ob)
...
E
J
O
T
Y

No copy of the input sequence is created to achieve this, so no additional memory or time is needed to produce the results. And taking every n-th object is done more efficiently than a % modulus test against an index from enumerate() could ever make it, or using range() to generate an index. That's because no further Python bytecode steps are needed to make those extra tests or index operations.

If you also needed to have the index of the items selected this way, add enumerate() back in by wrapping the iterable:

>>> for i, ob in islice(enumerate(iterable), n - 1, None, n):
...     print(i, ob)
...
4 E
9 J
14 O
19 T
24 Y

islice() beats any other solution hands-down if you need speed:

>>> from timeit import timeit
>>> from itertools import islice
>>> import random
>>> testdata = [random.randrange(1000) for _ in range(1000000)]  # 1 million random numbers
>>> def islice_loop(it):
...     for ob in islice(it, None, None, 5): pass
...
>>> def range_loop(it):
...     for i in range(0, len(it), 5): ob = it[i]
...
>>> def slice_loop(it):
...     for ob in it[::5]: pass
...
>>> def enumerate_test_loop(it):
...     for i, ob in enumerate(it):
...         if i % 5 == 0: pass
...
>>> def enumerate_list_slice_loop(it):
...     for i, ob in list(enumerate(it))[::5]: pass
...
>>> timeit('tf(t)', 'from __main__ import testdata as t, islice_loop as tf', number=1000)
4.194277995004086
>>> timeit('tf(t)', 'from __main__ import testdata as t, range_loop as tf', number=1000)
11.904250939987833
>>> timeit('tf(t)', 'from __main__ import testdata as t, slice_loop as tf', number=1000)
8.32347785399179
>>> timeit('tf(t)', 'from __main__ import testdata as t, enumerate_list_slice_loop as tf', number=1000)
198.1711291699903

So, for 1 million inputs, and 1000 tests, the enumerate() approach took sixteen times as much time as the islice() version, and the list(enumerate(...))[::n] copy-and-slice operation took almost 3 minutes to run the 1000 tests, clocking in at almost fifty times slower execution time. Don't ever use that option!

3 Comments

I'm not the downvoter, nor do I think it deserves it, but what issue is being sidestepped with this instead of range() with a step size, or what the OP title actually asked which is to add a counter to their loop?
@roganjosh: it's not clear to me why the OP is using the enumerate() call; if it is only used as a partial solution towards their problem, then it is no longer needed here. islice() would be more efficient than the other solutions.
@roganjosh: if you do want to add the index, then just replace iterable with enumerate(iterable) and you get the individual indices too, but again more efficiently.
0

Just use a range:

for i in range(0, size, n):
    print (i)

1 Comment

This only produces indices, and presumes a sequence as the input (with a set length). You'd have to use sequence[i] to get to the actual n-th value of the input sequence.
0

enumerate returns a sequence of tuples that look like (index, value)

Say your list is my_list. You could do something like

for index, value in enumerate(my_list):
    if index % n == 0: #it's an nth item
        print(value)

This is the python way of adding an index to a for loop.

Some Alternatives

If your goal is to do something with the list elements by n's, here are some alternative solutions that don't necessarily add an index to your loop, but will get you where you need to be:

Array Slicing

You could also use an array slice with a step

nth_elements = my_array[0::n]
for e in nth_elements:
    print(e)

The advantage is you're now working with a smaller list, at the cost of more memory and the time to make a copy, but that might be advantageous if you're doing several operations with it. It's quick to write and easy to read.

Range

You could so something similar with the range function

for index in range(0, len(my_list), n):
    print(n)

Note: if you're using python2, use xrange is preferred.

This just gets you the index, not the index and value. It's fast and memory efficient. range (or xrange) is lazy and you're using an index directly into the array, so you don't have to touch each element.

Itertools

If you would like a lazy sequence, you could use itertools. This might be useful if having the subsequence is too large to fit in memory comfortably.

from itertools import islice
for element in islice(my_list, 0, len(my_list), n):
    print(element)

This is fast and memory efficient, but it requires an import. Sure, it's from the standard library, but if you're writing code for a small device, maybe it's not available.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.