Есть два списка:
a = [1,2,3]
b = [4,5,6,7,8]
Нужно получить такой результат:
a + b = [5,7,9,7,8]
Надеюсь, смысл понятен.
Просто в голове одни костыли, хотелось бы найти более менее адекватное решение.
Ответы
Ответ 1
Ещё несколько вариантов решения:
longer = a if len(a) >= len(b) else b
c = [x+y for x,y in zip(a,b)] + longer[min(len(a), len(b)):]
или одной командой (менее читабельный вариант):
c = [x+y for x,y in zip(a,b)] + (a if len(a) >= len(b) else b)[min(len(a), len(b)):]
замеры времени выполнения для массивов длиной около 10^6:
In [110]: print(len(a), len(b))
1000000 1012345
In [104]: %timeit Sergey_Gornostaev(a, b)
267 ms ± 318 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [105]: %timeit nick_gabpe(a, b)
200 ms ± 168 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [106]: %timeit AtachiShadow1(a, b)
255 ms ± 558 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [107]: %timeit AtachiShadow2(a, b)
239 ms ± 1.69 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [108]: %timeit AtachiShadow3(a, b)
200 ms ± 604 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [109]: %timeit MaxU(a, b)
124 ms ± 1.26 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
setup:
from random import randint
a = [randint(0, 10**6) for _ in range(10**6)]
b = [randint(0, 10**6) for _ in range(10**6+12345)]
print(len(a), len(b))
# 1000000 1012345
def MaxU(a, b):
return [x+y for x,y in zip(a,b)] + (a if len(a) >= len(b) else b)[min(len(a),
len(b)):]
def nick_gabpe(a, b):
c=[]
for x, y in zip(a,b):
c+=[x+y]
if len(a) > len(b):
c+=a[len(a)-len(b)+1:]
elif len(a) < len(b):
c+=b[len(b)-len(a)+1:]
return c
def Sergey_Gornostaev(a, b):
return [sum(i) for i in zip_longest(a, b, fillvalue=0)]
def AtachiShadow1(a, b):
c = []
la = len(a)
lb = len(b)
maxab = max(la, lb)
minab = min(la, lb)
for i in range(maxab):
if i >= minab:
if la < lb:
c.append(0+b[i])
elif la > lb:
c.append(a[i]+0)
else:
c.append(a[i]+b[i])
return c
def AtachiShadow2(a, b):
c = []
la = len(a)
lb = len(b)
maxab = max(la, lb)
minab = min(la, lb)
for i in range(maxab):
try:
c.append(a[i]+b[i])
except IndexError:
if la < lb:
c.append(0+b[i])
elif la > lb:
c.append(a[i]+0)
return c
def AtachiShadow3(a, b):
c=[]
la = len(a)
lb = len(b)
for x, y in zip(a,b):
c+=[x+y]
if la > lb:
c+=a[la-lb+1:]
elif la < lb:
c+=b[lb-la+1:]
return c
Ответ 2
from itertools import zip_longest
c = [sum(i) for i in zip_longest(a, b, fillvalue=0)]
Ответ 3
Спасибо огромное автору за достаточно клёвую задачку, мне (как новичку) было очень
интересно разобраться как это работает) сообщение длинное, но надеюсь лёгкое в прочтении))):
Предложенный в комментарии zip и в ответе zip_longest я решил оставить на последние
варианты, а до них попробовать самостоятельно решить данный вопрос. И вот что я делал.
Первый вариант был таким:
a = [1, 2, 3]
b = [4, 5, 6, 7, 8]
c = []
for i in range(max(len(a), len(b))):
if i >= min(len(a), len(b)):
if len(a) == i + 1 and len(a) < len(b):
c.append(0 + b[i])
else:
c.append(a[i] + 0)
else:
c.append(a[i] + b[i])
print(c)
Он, конечно же делает то, что должен. Но мне пришла вторая мысль о более логическом
(как мне казалось) решении этого цикла. Второй вариант был таким:
a = [1, 2, 3]
b = [4, 5, 6, 7, 8]
c = []
for i in range(max(len(a), len(b))):
try:
c.append(a[i] + b[i])
except IndexError:
if i >= min(len(a), len(b)):
if len(a) == i + 1 and len(a) < len(b):
c.append(0 + bbb[i])
else:
c.append(a[i] + 0)
print(c)
С одной стороны, мне казалось, что до тех пор, пока меньший список не перестанет
итерироваться, будет выполняться тело try:. А когда меньший список закончится, возникнет
except IndexError и исполнится тело ошибки. Так как и этот вариант тоже делает то,
что задумано, логично было бы сравнить их по скорости выполнения. Я использовал timeit
и получил следующие результаты:
Вариант №1 - 8.045603216 на 1000000 (1млн) повторений
Вариант №2 - 9.076056240 на 1000000 (1млн) повторений
Я удивился. Я надеялся, что try: исключит лишнее выполнение if внутри цикла и тем
самым ускорится. Но оказалось наоборот.
После этого, я решил сравнить свои варианты с вариантом Sergey Gornostaev, и timeit
второй раз указал на неэффективность моих методов:
Вариант №1 - 8.045603216 на 1000000 (1млн) повторений
Вариант №2 - 9.076056240 на 1000000 (1млн) повторений
Sergey Gornostaev - 6.960991073 на 1000000 (1млн) повторений
Идея того, что на стэковерфлоу чаще всего предлагают "опытные" ответы в очередной
раз подтвердилась)))) 1 секунда выигрыша у Sergey Gornostaev)) Но я подумал, стандартная
библиотека == оптимизированный код. Я пошёл оптимизить свой код - в моих вариантах
функции len(), max(), min() запускаются в каждой итерации цикла, а зачем мне 5 раз
len(a) просить?)))) я тоже подумал что это немного тупо и надо исправить)))) вынес
их в переменные до цикла и подправил логику if.
Вариант №1.1:
a = [1, 2, 3]
b = [4, 5, 6, 7, 8]
c = []
la = len(a)
lb = len(b)
maxab = max(la, lb)
minab = min(la, lb)
for i in range(maxab):
if i >= minab:
if la < lb:
c.append(0+b[i])
elif la > lb:
c.append(a[i]+0)
else:
c.append(a[i]+b[i])
print(c)
Вариант №2.1:
a = [1, 2, 3]
b = [4, 5, 6, 7, 8]
c = []
la = len(a)
lb = len(b)
maxab = max(la, lb)
minab = min(la, lb)
for i in range(maxab):
try:
c.append(a[i]+b[i])
except IndexError:
if la < lb:
c.append(0+b[i])
elif la > lb:
c.append(a[i]+0)
print(c)
И да, оптимизация сработала:
Вариант №1.1 - 4.774997384 на 1000000 (1млн) повторений
Вариант №2.1 - 5.907061396 на 1000000 (1млн) повторений
Sergey Gornostaev - 6.960991073 на 1000000 (1млн) повторений
Почти двукратный прирост к скорости, за счёт правильного написания кода. И оба варианта
быстрее предложенной библиотеки)))
И когда я уже было хотел внести очередную правку в свой ответ, появился ответ автора:
a = [1,2,3]
b = [4,5,6,7,8]
c=[]
for x, y in zip(a,b):
c+=[x+y]
if len(a) > len(b):
c+=a[len(a)-len(b)+1:]
elif len(a) < len(b):
c+=b[len(b)-len(a)+1:]
print(c)
И что же timeit? Разнёс в щепки, все предыдущие варианты))):
Вариант №1.1 - 4.774997384 на 1000000 (1млн) повторений
Вариант №2.1 - 5.907061396 на 1000000 (1млн) повторений
Sergey Gornostaev - 6.960991073 на 1000000 (1млн) повторений
Автор - 3.151485595 на 1000000 (1млн) повторений
Выигрыш 1,5 секунды перед моим "быстым" оптимизированным вариантом. Идея хорошего
ответа на СО второй раз за один вопрос подтвердила себя))))))
НО! что ещё более круто в ответе самого автора, это использование zip. В ответе Sergey
Gornostaev и в моих вариантах используется концепция большего списка, то есть, for
будет делать столько циклов сколько элементов в большем списке - это есть ПЛОХО. Наводящий
вопрос - что будет, если один из списков будет пустым? А вот что:
a = []
Вариант №1.1 - 4.960243867 на 1000000 (1млн) повторений
Вариант №2.1 - 7.668313717 на 1000000 (1млн) повторений
Sergey Gornostaev - 6.865756772 на 1000000 (1млн) повторений
Автор - 1.848900008 на 1000000 (1млн) повторений
Вот это поворот!!! Вариант №1.1 остался таким же (0.2 на погрешность). А вот Вариант
№2.1 (try except) просто провалился - из-за того что a = [], каждая итерация for теперь
приводит к except, что и увеличило время выполнения. Вариант Sergey Gornostaev тоже,как
и 1.1 не изменился во времени исполнения (0.1 на погрешность). Но вот ответ автора
показал себя в полной красе, это по круче любой "чашки сахара" (из мультика про "вот
это поворот"))))) почти 3-х кратное превосходство на самым быстрым вариантом "длинного
списка"!
Но это ещё не всё!))) Я же помнил стандартная библиотека == оптимизированный код,
а код автора немножко не оптимизирован)))) функции len(a) и len(b) вызываются дважды
за выполнение. Значит их можно посчитать один раз в начале и потом пользоваться переменной)):
a = [1,2,3]
b = [4,5,6,7,8]
c=[]
la = len(a)
lb = len(b)
for x, y in zip(a,b):
c+=[x+y]
if la > lb:
c+=a[la-lb+1:]
elif la < lb:
c+=b[lb-la+1:]
И вот что говорит timeit:
a = [1,2,3]:
не оптимизированный код автора - 3.138068215
оптимизированный код автора - 2.908475165
a = []:
не оптимизированный код автора - 1.909198691
оптимизированный код автора - 1.642370166
Выигрыш на уровне 0.25 секунды у оптимизированного кода. И это уже точное 3-х кратное
превосходство над кодом "длинного списка"! Это круто))))
P.S.
1. Все измерения проводились на одном компьютере, и в приблизительно одинаковых условиях
(отличия мог вносить только открытый рядом Chrome), из этого следует что отношение
времени в выполнении разных логик будут другими на другом компьютере, а здесь показывается
лишь приблизительная ОТНОСИТЕЛЬНАЯ скорость выполнения логик.
2. Я не профессионал, даже не любитель, timeit я гуглил во время написания ответа.
Пока что ко мне больше применимо понятие "Script kiddie", но я учусь))))) Именно поэтому,
я предполагаю, что мои исследования в этом вопрос-ответе будут содержать ошибки и не
точности. И буду рад если о них мне расскажут более опытные программисты)))).
3. Ещё раз БОЛЬШОЕ спасибо автору вопроса-задачи и тем кто отвечал и комментировал
его, мне было приятно несколько раз удивляться своей несостоятельности, и так же было
приятно учиться на этом))
4. Приведённые измерения и отличия на 1 млн повторений могут вызвать закономерный
вопрос о том, а стоит ли париться из-за таких мелких отличий, тем более что речь идёт
о Python а не о C++ к примеру. И я считаю что каждый сам решит стоит ли оно того)))))))
Как минимум для разминки это можно было сделать)
Ответ 4
Ну и напишем свой велосипед:
a = [1,2,3]
b = [4,5,6,7,8]
c=[]
for x, y in zip(a,b):
c+=[x+y]
if len(a) > len(b):
c+=a[len(a)-len(b)+1:]
elif len(a) < len(b):
c+=b[len(b)-len(a)+1:]
print(c) # [5, 7, 9, 6, 7, 8]