The solutions provided by most here are all very similar in performance, and similar to your own.
The solution by @kellybundy is the only one that stands out and I doubt you'll find a faster one, given how minimal it is and the fact that it already relies on Python's fast internals. (please accept their answer, not this one, if you agree)
Consider:
from copy import deepcopy
from timeit import timeit
from random import choice
from collections import deque
chars = 'abcdefghijkl'
texts = [[choice(chars) for _ in range(3)] for _ in range(1000)]
nums = [n for n in range(1000)]
def combine0(xss, ys):
return xss # only here to show the cost of any overhead
def combine1(xss, ys):
result = []
for i, xs in enumerate(xss):
xs.append(ys[i])
result.append(xs)
return result
def combine2(xss, ys):
return [xs + [y] for xs, y in zip(xss, ys)]
def combine3(xss, ys):
return [[*xs, y] for xs, y in zip(xss, ys)]
def combine4(xss, ys):
result = []
for xs, y in zip(xss, ys):
xs.append(y)
result.append(xs)
return result
def combine5(xss, ys):
deque(map(list.append, xss, ys), 0)
return xss
assert combine1(deepcopy(texts), nums) == combine2(deepcopy(texts), nums) == combine3(deepcopy(texts), nums) == combine4(deepcopy(texts), nums) == combine5(deepcopy(texts), nums)
for _ in range(10):
for n, f in enumerate((combine0, combine1, combine2, combine3, combine4, combine5)):
copies = iter([deepcopy(texts) for _ in range(1000)])
time = timeit(lambda: f(next(copies), nums), number=1000) / 1000
print(f.__name__, f'{time * 1e6 :6.2f} µs')
print()
Result:
combine0 0.20 µs
combine1 82.28 µs
combine2 93.37 µs
combine3 73.44 µs
combine4 65.77 µs
combine5 16.27 µs
combine0 0.24 µs
combine1 75.62 µs
combine2 92.81 µs
combine3 91.56 µs
combine4 66.39 µs
combine5 17.73 µs
combine0 0.22 µs
combine1 84.68 µs
combine2 96.62 µs
combine3 87.32 µs
combine4 73.86 µs
combine5 15.44 µs
etc.
This shows that there's quite a bit of variation in runtime dependent on all sort of other factors, but there's a clear advantage for combine5
, which uses @kellybundy's solution.
The lines with 0
show the performance of the function that does nothing, to show that we're actually measuring the performance of the functions and not just the overhead of the calls etc.
Note: the deepcopy
s are there to avoid modifying the same list repeatedly and they are created before the test to avoid the creation of copies affecting the measurement.