Страницы

Поиск по вопросам

пятница, 27 декабря 2019 г.

Поэлементное сложение списков различной длины

#python #python_3x #списки


Есть два списка: 

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]

Комментариев нет:

Отправить комментарий