Pop multiple items from the beginning and end of a list
Question:
Suppose I have a list of items like this:
mylist=['a','b','c','d','e','f','g','h','i']
I want to pop two items from the left (i.e. a
and b
) and two items from the right (i.e. h
,i
). I want the most concise an clean way to do this. I could do it this way myself:
for x in range(2):
mylist.pop()
mylist.pop(0)
Any other alternatives?
Answers:
You could slice out a new list, keeping the old list as is:
mylist=['a','b','c','d','e','f','g','h','i']
newlist = mylist[2:-2]
newlist
now returns:
['c', 'd', 'e', 'f', 'g']
You can overwrite the reference to the old list too:
mylist = mylist[2:-2]
Both of the above approaches will use more memory than the below.
What you’re attempting to do yourself is memory friendly, with the downside that it mutates your old list, but popleft
is not available for lists in Python, it’s a method of the collections.deque
object.
This works well in Python 3:
for x in range(2):
mylist.pop(0)
mylist.pop()
In Python 2, use xrange and pop only:
for _ in xrange(2):
mylist.pop(0)
mylist.pop()
Fastest way to delete as Martijn suggests, (this only deletes the list’s reference to the items, not necessarily the items themselves):
del mylist[:2]
del mylist[-2:]
First 2 elements: myList[:2]
Last 2 elements: mylist[-2:]
So myList[2:-2]
If you don’t want to retain the values, you could delete the indices:
del myList[-2:], myList[:2]
This does still require that all remaining items are moved up to spots in the list. Two .popleft()
calls do require this too, but at least now the list object can handle the moves in one step.
No new list object is created.
Demo:
>>> myList = ['a','b','c','d','e','f','g','h','i']
>>> del myList[-2:], myList[:2]
>>> myList
['c', 'd', 'e', 'f', 'g']
However, from your use of popleft
I strongly suspect you are, instead, working with a collections.dequeue()
object instead. If so, *stick to using popleft()
, as that is far more efficient than slicing or del
on a list object.
From a performance point of view:
mylist = mylist[2:-2]
and del mylist[:2];del mylist[-2:]
are equivalent
- they are around 3 times faster than the first solution
for _ in range(2): mylist.pop(0); mylist.pop()
Code
iterations = 1000000
print timeit.timeit('''mylist=range(9)nfor _ in range(2): mylist.pop(0); mylist.pop()''', number=iterations)/iterations
print timeit.timeit('''mylist=range(9)nmylist = mylist[2:-2]''', number=iterations)/iterations
print timeit.timeit('''mylist=range(9)ndel mylist[:2];del mylist[-2:]''', number=iterations)/iterations
output
1.07710313797e-06
3.44465017319e-07
3.49956989288e-07
To me, this is the prettiest way to do it using a list comprehension:
>> mylist=['a','b','c','d','e','f','g','h','i']
>> newlist1 = [mylist.pop(0) for idx in range(2)]
>> newlist2 = [mylist.pop() for idx in range(2)]
That will pull the first two elements from the beginning and the last two elements from the end of the list. The remaining items stay in the list.
Python3 has something cool, similar to rest in JS (but a pain if you need to pop out a lot of stuff)
mylist=['a','b','c','d','e','f','g','h','i']
_, _, *mylist, _, _ = mylist
mylist == ['c', 'd', 'e', 'f', 'g'] # true
mylist = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o']
new_list = [mylist.pop(0) for _ in range(6) if len(mylist) > 0]
>>> new_list
['a', 'b', 'c', 'd', 'e', 'f']
new_list = [mylist.pop(0) for _ in range(6) if len(mylist) > 0]
>>> new_list
['g', 'h', 'i', 'j', 'k', 'l']
new_list = [mylist.pop(0) for _ in range(6) if len(mylist) > 0]
>>> new_list
['m', 'n', 'o']
Suppose I have a list of items like this:
mylist=['a','b','c','d','e','f','g','h','i']
I want to pop two items from the left (i.e. a
and b
) and two items from the right (i.e. h
,i
). I want the most concise an clean way to do this. I could do it this way myself:
for x in range(2):
mylist.pop()
mylist.pop(0)
Any other alternatives?
You could slice out a new list, keeping the old list as is:
mylist=['a','b','c','d','e','f','g','h','i']
newlist = mylist[2:-2]
newlist
now returns:
['c', 'd', 'e', 'f', 'g']
You can overwrite the reference to the old list too:
mylist = mylist[2:-2]
Both of the above approaches will use more memory than the below.
What you’re attempting to do yourself is memory friendly, with the downside that it mutates your old list, but popleft
is not available for lists in Python, it’s a method of the collections.deque
object.
This works well in Python 3:
for x in range(2):
mylist.pop(0)
mylist.pop()
In Python 2, use xrange and pop only:
for _ in xrange(2):
mylist.pop(0)
mylist.pop()
Fastest way to delete as Martijn suggests, (this only deletes the list’s reference to the items, not necessarily the items themselves):
del mylist[:2]
del mylist[-2:]
First 2 elements: myList[:2]
Last 2 elements: mylist[-2:]
So myList[2:-2]
If you don’t want to retain the values, you could delete the indices:
del myList[-2:], myList[:2]
This does still require that all remaining items are moved up to spots in the list. Two .popleft()
calls do require this too, but at least now the list object can handle the moves in one step.
No new list object is created.
Demo:
>>> myList = ['a','b','c','d','e','f','g','h','i']
>>> del myList[-2:], myList[:2]
>>> myList
['c', 'd', 'e', 'f', 'g']
However, from your use of popleft
I strongly suspect you are, instead, working with a collections.dequeue()
object instead. If so, *stick to using popleft()
, as that is far more efficient than slicing or del
on a list object.
From a performance point of view:
mylist = mylist[2:-2]
anddel mylist[:2];del mylist[-2:]
are equivalent- they are around 3 times faster than the first solution
for _ in range(2): mylist.pop(0); mylist.pop()
Code
iterations = 1000000
print timeit.timeit('''mylist=range(9)nfor _ in range(2): mylist.pop(0); mylist.pop()''', number=iterations)/iterations
print timeit.timeit('''mylist=range(9)nmylist = mylist[2:-2]''', number=iterations)/iterations
print timeit.timeit('''mylist=range(9)ndel mylist[:2];del mylist[-2:]''', number=iterations)/iterations
output
1.07710313797e-06
3.44465017319e-07
3.49956989288e-07
To me, this is the prettiest way to do it using a list comprehension:
>> mylist=['a','b','c','d','e','f','g','h','i']
>> newlist1 = [mylist.pop(0) for idx in range(2)]
>> newlist2 = [mylist.pop() for idx in range(2)]
That will pull the first two elements from the beginning and the last two elements from the end of the list. The remaining items stay in the list.
Python3 has something cool, similar to rest in JS (but a pain if you need to pop out a lot of stuff)
mylist=['a','b','c','d','e','f','g','h','i']
_, _, *mylist, _, _ = mylist
mylist == ['c', 'd', 'e', 'f', 'g'] # true
mylist = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o']
new_list = [mylist.pop(0) for _ in range(6) if len(mylist) > 0]
>>> new_list
['a', 'b', 'c', 'd', 'e', 'f']
new_list = [mylist.pop(0) for _ in range(6) if len(mylist) > 0]
>>> new_list
['g', 'h', 'i', 'j', 'k', 'l']
new_list = [mylist.pop(0) for _ in range(6) if len(mylist) > 0]
>>> new_list
['m', 'n', 'o']