简体   繁体   中英

What is the most efficient way to push and pop a list in Python?

In Python how do I write code which shifts off the last element of a list and adds a new one to the beginning - to run as fast as possible at execution?

There are good solutions involving the use of append, rotate etc but not all may translate to fast execution.

Don't use a list.

A list can do fast inserts and removals of items only at its end. You'd use pop(-1) and append , and you'd end up with a stack.

Instead, use collections.deque , which is designed for efficient addition and removal at both ends. Working on the "front" of a deque uses the popleft and appendleft methods. Note, "deque" means "double ended queue", and is pronounced "deck".

L = [1, 2, 3]
L.pop() # returns 3, L is now [1, 2]
L.append(4) # returns None, L is now [1, 2, 4]
L.insert(0, 5) # returns None, L is now [5, 1, 2, 4]
L.remove(2) # return None, L is now [5, 1, 4]
del(L[0]) # return None, L is now [1, 4]
L.pop(0) # return 1, L is now [4]

I ran some benchmarks for you. Here are the results.

TL;DR You probably want to use a deque . Otherwise, insert / append , or pop / del are fine.

Adding to the end

from collections import deque
import perfplot

# Add to end

def use_append(n):
    "adds to end"
    a = [1,2,3,4,5,6,7,8,9,10]*n
    a.append(7)
    return 1

def use_insert_end(n):
    "adds to end"
    a = [1,2,3,4,5,6,7,8,9,10]*n
    a.insert(len(a),7)
    return 1

def use_add_end(n):
    "adds to end"
    a = [1,2,3,4,5,6,7,8,9,10]*n
    a = a + [7]
    return 1

perfplot.show(
    setup=lambda n: n,  # or simply setup=numpy.random.rand
    kernels=[
        lambda a: use_append(a),
        lambda a: use_insert_end(a),
        lambda a: use_add_end(a),
    ],
    labels=["use_append", "use_insert_end", "use_add_end"],
    n_range=[2 ** k for k in range(15)],
    xlabel="len(a)",
)

add_end

Remove from end

# Removing from the end

def use_pop(n):
    "removes from end"
    a = [1,2,3,4,5,6,7,8,9,10]*n
    a.pop()
    return 1

def use_del_last(n):
    "removes from end"
    a = [1,2,3,4,5,6,7,8,9,10]*n
    del(a[-1])
    return 1

def use_index_to_end(n):
    "removes from end"
    a = [1,2,3,4,5,6,7,8,9,10]*n
    a = a[:-1]
    return 1

perfplot.show(
    setup=lambda n: n,
    kernels=[
        lambda a: use_pop(a),
        lambda a: use_del_last(a),
        lambda a: use_index_to_end(a),
    ],
    labels=["use_pop", "use_del_last", "use_index_to_end"],
    n_range=[2 ** k for k in range(20)],
    xlabel="len(a)",
)

remove_end

Adding to the beginning

# Add to beginning

def use_insert(n):
    "adds to beginning"
    a = [1,2,3,4,5,6,7,8,9,10]*n
    a.insert(0,7)
    return 1

def use_deque_appendleft(n):
    "adds to beginning"
    a = [1,2,3,4,5,6,7,8,9,10]*n
    a = deque(a)
    a.appendleft(7)
    return 1

def use_add_start(n):
    "adds to beginning"
    a = [1,2,3,4,5,6,7,8,9,10]*n
    a = [7] + a
    return 1

perfplot.show(
    setup=lambda n: n,  # or simply setup=numpy.random.rand
    kernels=[
        lambda a: use_insert(a),
        lambda a: use_deque_appendleft(a),
        lambda a: use_add_start(a),
    ],
    labels=["use_insert", "use_deque_appendleft","use_add_start"],
    n_range=[2 ** k for k in range(15)],
    xlabel="len(a)",
)

add_start

Removing from the beginning

# Remove from beginning

def use_del_first(n):
    "removes from beginning"
    a = [1,2,3,4,5,6,7,8,9,10]*n
    del(a[0])
    return 1


def use_deque_popleft(n):
    "removes from beginning"
    a = [1,2,3,4,5,6,7,8,9,10]*n
    a = deque(a)
    a.popleft()
    return 1


def use_index_start(n):
    "removes from beginning"
    a = [1,2,3,4,5,6,7,8,9,10]*n
    a = a[1:]
    return 1


perfplot.show(
    setup=lambda n: n,  # or simply setup=numpy.random.rand
    kernels=[
        lambda a: use_del_first(a),
        lambda a: use_deque_popleft(a),
        lambda a: use_index_start(a),
    ],
    labels=["use_del_first", "use_deque_popleft", "use_index_start"],
    n_range=[2 ** k for k in range(15)],
    xlabel="len(a)",
)

remove_start


Edit

Take these results with a grain of salt. Given how perfplot works, the remove methods would get run multiple times, while setup is only run once. Hence, the list (or deque) needs to be generated locally in each function, which adds to run time.

I've modified the add methods below, and run a separate comparison for deque, to compare the effect of generating lists locally within the functions.

Deque setup difference


def gen_deque(n):
    a = [1,2,3,4,5,6,7,8,9,10]*n if n > 0 else [1,2,3]
    return deque(a)


def use_deque_appendleft(a):
    "adds to beginning"
    a.appendleft(7)
    return 1


def use_deque_appendleft_original(a):
    "adds to beginning"
    a = [1,2,3,4,5,6,7,8,9,10]*(len(a)//10)
    a = deque(a)
    a.appendleft(7)
    return 1

perfplot.show(
    setup=lambda n: gen_deque(n),  # or simply setup=numpy.random.rand
    kernels=[
        lambda a: use_deque_appendleft(a),
        lambda a: use_deque_appendleft_original(a),
    ],
    labels=["use_deque_appendleft", "use_deque_appendleft_original"],
    n_range=[2 ** k for k in range(15)],
    xlabel="len(a)",
)

deque_diff

Add to end

# Add to end

def gen_data(n):
    return [1,2,3,4,5,6,7,8,9,10]*n if n > 0 else [1,2,3]

def use_append(a):
    "adds to end"
#     a = [1,2,3,4,5,6,7,8,9,10]*n
    a.append(7)
    return 1

def use_insert_end(a):
    "adds to end"
#     a = [1,2,3,4,5,6,7,8,9,10]*n
    a.insert(len(a),7)
    return 1

def use_add_end(a):
    "adds to end"
#     a = [1,2,3,4,5,6,7,8,9,10]*n
    a = a + [7]
    return 1

perfplot.show(
    setup=lambda n: gen_data(n),  # or simply setup=numpy.random.rand
    kernels=[
        lambda a: use_append(a),
        lambda a: use_insert_end(a),
        lambda a: use_add_end(a),
    ],
    labels=["use_append", "use_insert_end", "use_add_end"],
    n_range=[2 ** k for k in range(15)],
    xlabel="len(a)",
)

add_end2

Add to start

# Add to beginning

def gen_data(n):
    return [1,2,3,4,5,6,7,8,9,10]*n if n > 0 else [1,2,3]


def use_insert(a):
    "adds to beginning"
    a.insert(0,7)
    return 1

def use_deque_appendleft(a):
    "adds to beginning"
    a = deque(a)
    a.appendleft(7)
    return 1

def use_add_start(a):
    "adds to beginning"
    a = [7] + a
    return 1

perfplot.show(
    setup=lambda n: gen_data(n),  # or simply setup=numpy.random.rand
    kernels=[
        lambda a: use_insert(a),
        lambda a: use_deque_appendleft(a),
        lambda a: use_add_start(a),
    ],
    labels=["use_insert", "use_deque_appendleft","use_add_start"],
    n_range=[2 ** k for k in range(5)],
    xlabel="len(a)",
)

add_start2

Conclusion

Insert and append have similar performance, and using a deque seems to have better performance than insert. As for del / pop / deque's popleft , it seems that del and pop have similar performance, but it's hard to tell if deque's popleft would be better, considering the overhead of generating lists / deques within each function for using perfplot .

You can use insert method of python list object.

l = [1, 2, 3]
#lets insert 10 at biggining, which means at index 0
l.insert(0, 10)
print(l) # this will print [10, 1, 2, 3]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM