8

So I was wondering how I can, using Python 2.7, most efficiently take a list of values used to represent indices like this: (but with a length of up to 250,000+)

indices = [2, 4, 5]

and remove that list of indices from a larger list like this: (3,000,000+ items)

numbers = [2, 6, 12, 20, 24, 40, 42, 51]

to get a result like this:

[2, 6, 20, 42, 51]

I'm looking for an efficient solution more than anything else. I know there are many ways to do this, however that's not my problem. Efficiency is. Also, this operation will have to be done many times and the lists will both get exponentially smaller. I do not have an equation to represent how much smaller they will get over time.

edit:

Numbers must remain sorted in a list the entire time or return to being sorted after the indices have been removed. The list called indices can either be sorted or not sorted. It doesn't even have to be in a list.

6 Answers6

7

You may want to consider using the numpy library for efficiency (which if you're dealing with lists of integers may not be a bad idea anyway):

>>> import numpy as np
>>> a = np.array([2, 6, 12, 20, 24, 40, 42, 51])
>>> np.delete(a, [2,4,5])
array([ 2,  6, 20, 42, 51])

Notes on np.delete: http://docs.scipy.org/doc/numpy/reference/generated/numpy.delete.html

It might also be worth at looking at keeping the main array as is, but maintaining a masked array (haven't done any speed tests on that either though...)

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Jon Clements
  • 138,671
  • 33
  • 247
  • 280
  • I tested this using my test suite in my answer and it's not significantly faster than a list comprehension. (0.53 seconds versus 0.59 seconds) – FogleBird Nov 27 '12 at 00:51
  • Last time I tried installing numpy, I couldn't find a 64-bit build for Mac OS X Lion. Only 32-bit. And I would really prefer to use 64-bit. I could be wrong though. They may have a 64-bit build that I haven't seen. – Steven Hicken Nov 27 '12 at 00:52
  • @StevenHicken might also be worth looking at masked arrays – Jon Clements Nov 27 '12 at 00:57
  • I just took a look at masked arrays. They may come in use but I would have to redesign the algorithm I'm working on. – Steven Hicken Nov 27 '12 at 02:16
6

I have a suspicion that taking whole slices between the indices might be faster than the list comprehension

def remove_indices(numbers, indices):
    result = []
    i=0
    for j in sorted(indices):
        result += numbers[i:j]
        i = j+1
    result += numbers[i:]
    return result
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
4

Another option:

>>> numbers = [2, 6, 12, 20, 24, 40, 42, 51]
>>> indicies = [2, 4, 5]
>>> offset = 0
>>> for i in indicies:
...     del numbers[i - offset]
...     offset += 1
...
>>> numbers
[2, 6, 20, 42, 51]

Edit:

So after being hopelessly wrong on this answer, I benchmarked each of the different approaches:

enter image description here

Horizontal axis is number of items, vertical is time in seconds.

The fastest option is using slicing to build a new list (from @gnibbler):

def using_slices(numbers, indices):
    result = []
    i = 0
    for j in indices:
        result += numbers[i:j]
        i = j + 1
    result += numbers[i:]

Surprisingly it and "sets" (@Eric) beat numpy.delete (@Jon Clements)

Here's the script I used, perhaps I've missed something.

bradley.ayers
  • 37,165
  • 14
  • 93
  • 99
3

Here's my first approach.

def remove_indices(numbers, indices):
    indices = set(indices)
    return [x for i, x in enumerate(numbers) if i not in indices]

Here's a test module to test it under the conditions you specified. (3 million elements with 250k to remove)

import random

def create_test_set():
    numbers = range(3000000)
    indices = random.sample(range(3000000), 250000)
    return numbers, indices

def remove_indices(numbers, indices):
    indices = set(indices)
    return [x for i, x in enumerate(numbers) if i not in indices]

if __name__ == '__main__':
    import time
    numbers, indices = create_test_set()
    a = time.time()
    numbers = remove_indices(numbers, indices)
    b = time.time()
    print b - a, len(numbers)

It takes around 0.6 seconds on my laptop. You might consider making the indices a set beforehand if you'll be using it multiple times.

(FWIW bradley.ayers solution took longer than I was willing to wait.)

Edit: This is slightly faster: (0.55 seconds)

def remove_indices(numbers, indices):
    return [numbers[i] for i in xrange(len(numbers)) if i not in indices]
FogleBird
  • 74,300
  • 25
  • 125
  • 131
2

Not that efficient, but a different approach

indices = set([2, 4, 5])

result = [x for i,x in enumerate(numbers) if i not in indices]
Eric
  • 95,302
  • 53
  • 242
  • 374
1

Another different approach to achieve that:

>>> numbers = [2, 6, 12, 20, 24, 40, 42, 51]
>>> indices = [2, 4, 5]
>>> [item for item in numbers if numbers.index(item) not in indices]
[2, 6, 20, 42, 51]
ettanany
  • 19,038
  • 9
  • 47
  • 63