Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: speed up removal of every n-th element from list

I'm trying to solve this programming riddle and although the solution (see code below) works correctly, it is too slow for succesful submission.

  • Any pointers as how to make this run faster (removal of every n-th element from a list)?
  • Or suggestions for a better algorithm to calculate the same; seems I can't think of anything else than brute-force for now...

Basically, the task at hand is:

GIVEN:
L = [2,3,4,5,6,7,8,9,10,11,........]
1. Take the first remaining item in list L (in the general case 'n'). Move it to 
   the 'lucky number list'. Then drop every 'n-th' item from the list.
2. Repeat 1

TASK:
Calculate the n-th number from the 'lucky number list' ( 1 <= n <= 3000)

My original code (it calculated the 3000 first lucky numbers in about a second on my machine - unfortunately too slow):

"""
SPOJ Problem Set (classical) 1798. Assistance Required
URL: http://www.spoj.pl/problems/ASSIST/
"""

sieve = range(3, 33900, 2)
luckynumbers = [2]

while True:
    wanted_n = input()
    if wanted_n == 0:
        break

    while len(luckynumbers) < wanted_n:
        item = sieve[0]
        luckynumbers.append(item)
        items_to_delete = set(sieve[::item])
        sieve = filter(lambda x: x not in items_to_delete, sieve)
    print luckynumbers[wanted_n-1]

EDIT: thanks to the terrific contributions of Mark Dickinson, Steve Jessop and gnibbler, I got at the following, which is quite a whole lot faster than my original code (and succesfully got submitted at http://www.spoj.pl with 0.58 seconds!)...

sieve = range(3, 33810, 2)
luckynumbers = [2]

while len(luckynumbers) < 3000:
    if len(sieve) < sieve[0]:
        luckynumbers.extend(sieve)
        break
    luckynumbers.append(sieve[0])
    del sieve[::sieve[0]]

while True:
    wanted_n = input()
    if wanted_n == 0:
        break
    else:
        print luckynumbers[wanted_n-1]
like image 503
ChristopheD Avatar asked Mar 18 '10 22:03

ChristopheD


People also ask

How do you remove every nth element from a list in Python?

To remove every nth element of a list in Python, utilize the Python del keyword to delete elements from the list and slicing. For your slice, you want to pass n for the slice step size. For example, if you have a list and you to remove every 2nd element, you would delete the slice defined by [1::2] as shown below.

How do you remove all occurrences of an element in a list in Python?

Use the remove() Function to Remove All the Instances of an Element From a List in Python. The remove() function only removes the first occurrence of the element. If you want to remove all the occurrence of an element using the remove() function, you can use a loop either for loop or while loop.

Can we remove multiple elements from list in Python?

Multiple elements can be deleted from a list in Python, based on the knowledge we have about the data. Like, we just know the values to be deleted or also know the indexes of those values.


2 Answers

This series is called ludic numbers

__delslice__ should be faster than __setslice__+filter

>>> L=[2,3,4,5,6,7,8,9,10,11,12]
>>> lucky=[]
>>> lucky.append(L[0])
>>> del L[::L[0]]
>>> L
[3, 5, 7, 9, 11]
>>> lucky.append(L[0])
>>> del L[::L[0]]
>>> L
[5, 7, 11]

So the loop becomes.

while len(luckynumbers) < 3000:
    item = sieve[0]
    luckynumbers.append(item)
    del sieve[::item] 

Which runs in less than 0.1 second

like image 194
John La Rooy Avatar answered Sep 19 '22 07:09

John La Rooy


Try using these two lines for the deletion and filtering, instead of what you have; filter(None, ...) runs considerably faster than the filter(lambda ...).

sieve[::item] = [0]*-(-len(sieve)//item)
sieve = filter(None, sieve)

Edit: much better to simply use del sieve[::item]; see gnibbler's solution.

You might also be able to find a better termination condition for the while loop: for example, if the first remaining item in the sieve is i then the first i elements of the sieve will become the next i lucky numbers; so if len(luckynumbers) + sieve[0] >= wanted_n you should already have computed the number you need---you just need to figure out where in sieve it is so that you can extract it.

On my machine, the following version of your inner loop runs around 15 times faster than your original for finding the 3000th lucky number:

while len(luckynumbers) + sieve[0] < wanted_n:
    item = sieve[0]
    luckynumbers.append(item)
    sieve[::item] = [0]*-(-len(sieve)//item)
    sieve = filter(None, sieve)
print (luckynumbers + sieve)[wanted_n-1]
like image 35
Mark Dickinson Avatar answered Sep 20 '22 07:09

Mark Dickinson