38

In many languages we can do something like:

for (int i = 0; i < value; i++)
{
    if (condition)
    {
        i += 10;
    }
}

How can I do the same in Python? The following (of course) does not work:

for i in xrange(value):
    if condition:
        i += 10

I could do something like this:

i = 0
while i < value:
  if condition:
    i += 10
  i += 1

but I'm wondering if there is a more elegant (pythonic?) way of doing this in Python.

Mefaso
  • 94
  • 9
Oscar Mederos
  • 29,016
  • 22
  • 84
  • 124

9 Answers9

30

Use continue.

for i in xrange(value):
    if condition:
        continue

If you want to force your iterable to skip forwards, you must call .next().

>>> iterable = iter(xrange(100))
>>> for i in iterable:
...     if i % 10 == 0:
...         [iterable.next() for x in range(10)]
... 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
[41, 42, 43, 44, 45, 46, 47, 48, 49, 50]
[61, 62, 63, 64, 65, 66, 67, 68, 69, 70]
[81, 82, 83, 84, 85, 86, 87, 88, 89, 90]

As you can see, this is disgusting.

bradley.ayers
  • 37,165
  • 14
  • 93
  • 99
  • 1
    Please, check my last edit. I changed it to skip 10 values of *i* instead of only one. – Oscar Mederos Apr 01 '11 at 05:02
  • It depends on what your trying to do, what are you looping over, and why are you using i as the counter? The more pythonic way may be not to use the "counter" at all. It's hard to say without a more detailed example. – monkut Apr 01 '11 at 05:05
  • 1
    Iterators don't allow you to 'jump ahead' by an arbitrary number of steps. The only way to skip ahead is to call `.next()` on your iterator the number of times you want to skip. – bradley.ayers Apr 01 '11 at 05:07
  • @brad.ayers can you post an example of how to call `.next()` in the code you posted? – Oscar Mederos Apr 01 '11 at 05:18
  • @brad.ayers thanks. I just wanted to see another way of doing this, although I think I will keep using the `while` ;) – Oscar Mederos Apr 01 '11 at 05:34
  • 3
    @brad.ayers, itertools.islice does the jump ahead on an iterator. Spend some time looking at itertools, you will fall in love. islice handles the next() calls for you. – kevpie Apr 01 '11 at 05:49
  • Your output is slightly off. It prints "0, 11,12,13,14,15,16,17,18,19,20,31,32" etc – N_A Jul 13 '12 at 13:51
  • @mydogisbox How so? I just copy/pasted the code into a Python interpreter and I get the output I have in my answer, please elaborate. – bradley.ayers Jul 15 '12 at 12:05
  • @bradley.ayers Interesting, maybe there is a bug in the iron python interpreter I'm using. It gives me the output I posted. – N_A Jul 15 '12 at 20:48
23

Create the iterable before the loop.

Skip one by using next on the iterator

it = iter(xrange(value))
for i in it:
    if condition:
        i = next(it)

Skip many by using itertools or recipes based on ideas from itertools.

itertools.dropwhile()

it = iter(xrange(value))
for i in it:
    if x<5:
        i = dropwhile(lambda x: x<5, it)

Take a read through the itertools page, it shows some very common uses of working with iterators.

itertools islice

it = islice(xrange(value), 10)
for i in it:
    ...do stuff with i...
kevpie
  • 25,206
  • 2
  • 24
  • 28
  • the first code returns the following exception: `TypeError: xrange object is not an iterator`. I think it will require to specify: `it = xrange(value).__iter__()` – Oscar Mederos Apr 01 '11 at 05:28
  • @oscar, thx. I was just going off the top of my head. Just wrap it in an iter() call to instantiate an iterator. – kevpie Apr 01 '11 at 05:34
  • 1
    @oscar, takewhile was not quite what you may have been looking for, I changed it to dropwhile. If you simply want skip over a bunch of an iterator, islice can do that quite affectively if it is a qtantity. – kevpie Apr 01 '11 at 05:41
6

It's a very old question, but I find the accepted answer is not totally stisfactory:

  • first, after the if ... / [next()...] sequence, the value of i hasn't changed. In your first example, it has.
  • second, the list comprehension is used to produce a side-effect. This should be avoided.
  • third, there might be a faster way to achieve this.

Using a modified version of consume in itertools recipes, you can write:

import itertools

def consume(it, n):
    return next(itertools.islice(it, n-1, n), None)

it = iter(range(20))
for i in it:
    print(i, end='->')
    if i%4 == 0:
        i = consume(it, 5)
    print(i)

As written in the doctstring of consume, the iterator is consumed at C speed (didn't benchmark though). Output:

0->5
6->6
7->7
8->13
14->14
15->15
16->None

With a minor modification, one can get 21 instead of None, but I think this isnot a good idea because this code does work with any iterable (otherwise one would prefer the while version):

import string
it = iter(string.ascii_lowercase) # a-z
for x in it:
    print(x, end="->")
    if x in set('aeiouy'):
        x = consume(it, 2) # skip the two letters after the vowel
    print(x)

Output:

a->c
d->d
e->g
h->h
i->k
l->l
m->m
n->n
o->q
r->r
s->s
t->t
u->w
x->x
y->None
jferard
  • 7,835
  • 2
  • 22
  • 35
  • it should be `n` not in `n-1` in consume() – Smart Manoj Sep 25 '21 at 05:26
  • @SmartManoj I just tested it with : `>>> it = iter(range(10)) / >>> list(it) / [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] / >>> it = iter(range(10)) / >>> consume(it, 4) / 3 / >>> list(it) / [4, 5, 6, 7, 8, 9]`. Seems ok to me. – jferard Sep 25 '21 at 12:09
  • if it is n, consume returns None else the last consumed element – Smart Manoj Sep 28 '21 at 13:54
4

Itertools has a recommended way to do this: https://docs.python.org/3.7/library/itertools.html#itertools-recipes

import collections
def tail(n, iterable):
    "Return an iterator over the last n items"
    # tail(3, 'ABCDEFG') --> E F G
    return iter(collections.deque(iterable, maxlen=n))

Now you can do:

for i in tail(5, range(10)):
    print(i)

to get

5
6
7
8
9
Thomas Ahle
  • 30,774
  • 21
  • 92
  • 114
  • This does kind of the opposite of the request. The request is to "skip N items", but this answer shows how to skip *all but* N items. Obv this isn't too difficult to account for if the total number of items is known ahead-of-time, but that isn't always known. It also wastefully forces all items to be consumed immediately. (It isn't wasteful if you *actually want* tail, but it's wasteful compared to what we really want, which is `isplice(iter, N, None)`.) – Josh Jan 26 '21 at 14:11
  • @tho What about the consume function in that recipe? – Smart Manoj Sep 25 '21 at 05:22
  • @josh isplice from? – Smart Manoj Sep 25 '21 at 05:22
1

I am hoping I am not answering this wrong... but this is the simplest way I have come across:

for x in range(0,10,2):
    print x

output should be something like this:

0
2
4
6
8

The 2 in the range parameter's is the jump value

user282190
  • 300
  • 4
  • 20
0

There are a few ways to create iterators, but the custom iterator class is the most extensible:

class skip_if:   # skip_if(object) for python2
    """
    iterates through iterable, calling skipper with each value
    if skipper returns a positive integer, that many values are
    skipped
    """
    def __init__(self, iterable, skipper):
        self.it = iter(iterable)
        self.skip = skipper
    def __iter__(self):
        return self
    def __next__(self):   # def next(self): for python2
        value = next(self.it)
        for _ in range(self.skip(value)):
            next(self.it, None)
        return value

and in use:

>>> for i in skip_if(range(1,100), lambda n: 10 if not n%10 else 0):
...   print(i, end=', ')
... 
 1,  2,  3,  4,  5,  6,  7,  8,  9, 10,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
Community
  • 1
  • 1
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
0

Does a generator function here is rebundant? Like this:

def filterRange(range, condition):
x = 0
while x < range:
    x = (x+10) if condition(x) else (x + 1)
    yield x

if __name__ == "__main__":
for i in filterRange(100, lambda x: x > 2):
    print i
0

Easiest way is to use more_itertools.consume(). See https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.consume.

import more_itertools

x = iter(range(10))

# skip the next four items
more_itertools.consume(x, 4)
y = list(x)
print(y)

will result in

[4, 5, 6, 7, 8, 9]

This becomes super helpful if you are iterating through a file and need to skip a bunch of lines. Something like this:

with open("test.txt", "r") as file:
    while line:= next(file):

        # Look for "magic_word" and then skip 10 lines.
        if "magic_word" in line:
            more_itertools.consume(file, 10)
    
        # do other stuff with line
Tom Johnson
  • 1,793
  • 1
  • 13
  • 31
-1

I think you have to use a while loop for this...for loop loops over an iterable..and you cannot skip next item like how you want to do it here

Rafi
  • 805
  • 6
  • 12