Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - Complement of range-based sublist selection

Tags:

python

list

range

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.

like image 478
Acorbe Avatar asked Sep 21 '26 19:09

Acorbe


2 Answers

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
like image 109
Corley Brigman Avatar answered Sep 23 '26 07:09

Corley Brigman


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)):
like image 45
Martijn Pieters Avatar answered Sep 23 '26 09:09

Martijn Pieters



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!