What's the best way of skip N values of the iteration variable in Python?
Question:
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.
Answers:
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.
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
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.
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...
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
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
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,
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
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
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.
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.
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
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.
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...
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
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
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,
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
It’s a very old question, but I find the accepted answer is not totally stisfactory:
- first, after the
if ... / [next()...]
sequence, the value ofi
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