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)",
)

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)",
)

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)",
)

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)",
)

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)",
)

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 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)",
)

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
.