I was wondering if there is a python "core" syntax to handle complement of range-based selections.
Say that
a = [0,1,2,3,4,5,6]
then, for instance,
offset = 1
step = 3
a[offset::step] = [1,4].
My question hence is:
"Can I do like
a[~(offset::step)] == [0,2,3,5,6]
without using ifs?"
Or, "what is the most pythonic way to handle this?"
Addendum:
say that I have to do this sub-sampling operation for thousands of lists (indeed trajectories of particles) of variable size (i.e. trajectories of variable time length). So I cannot pre-calculate the correct index set.
sets are (usually) about an order of magnitude faster, even if you don't populate the indices ahead of time:
r100 = range(100)
r2 = range(3, 40, 3)
# Find indices in r100 that aren't in r2.
# This is a set difference (or symmetric difference)
## Set methods
# Precalculated is fastest:
sr100 = set(r100)
sr2 = set(r2)
%timeit sr100 - sr2
100000 loops, best of 3: 3.84 us per loop
# Non-precalculated is still faster:
%timeit set(range(100)) ^ set(range(3,40,3))
100000 loops, best of 3: 9.76 us per loop
%timeit set(xrange(100)) ^ set(xrange(3,40,3))
100000 loops, best of 3: 8.84 us per loop
# Precalculating the original indices still helps, if you can hold it in memory:
%timeit sr100 ^ set(xrange(3,40,3))
100000 loops, best of 3: 4.87 us per loop
# This is true even including converting back to list, and sorting (if necessary):
%timeit [x for x in sr100 ^ set(xrange(3,40,3))]
100000 loops, best of 3: 9.02 us per loop
%timeit sorted(x for x in sr100 ^ set(xrange(3,40,3)))
100000 loops, best of 3: 15 us per loop
## List comprehension:
# Precalculated indices
%timeit [x for x in r100 if x not in r2]
10000 loops, best of 3: 30.5 us per loop
# Non-precalculated indices, using xrange
%timeit [x for x in xrange(100) if x not in xrange(3, 40, 3)]
10000 loops, best of 3: 65.8 us per loop
# The cost appears to be in the second xrange?
%timeit [x for x in r100 if x not in xrange(3, 40, 3)]
10000 loops, best of 3: 64.3 us per loop
%timeit [x for x in xrange(100) if x not in r2]
10000 loops, best of 3: 29.9 us per loop
# xrange is not really any faster than range here - uses less memory, but still have
# to walk through entire list
%timeit [x for x in range(100) if x not in range(3, 40, 3)]
10000 loops, best of 3: 63.5 us per loop
You'd have to generate the indices, then use a list comprehension to select all values not matching those indices. Use a range() object for an efficient method to test for indices (xrange() in python 2):
indices = range(offset, len(a), step)
[v for i, v in enumerate(a) if i not in indices]
The range() object in Python 3 (xrange() in Python 2) only holds the start, end, and step values, in tests simply do a quick calculation if the tested value is part of the range.
Demo:
>>> a = [0, 1, 2, 3, 4, 5, 6]
>>> offset, step = 1, 3
>>> indices = range(offset, len(a), step)
>>> indices
range(1, 7, 3)
>>> [v for i, v in enumerate(a) if i not in indices]
[0, 2, 3, 5, 6]
Yes, this still requires using an if statement, but the test is cheap and can be incorporated in a generator expression as needed:
for i in (v for i, v in enumerate(a) if i not in range(offset, len(a), step)):
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With