Python: найшвидший спосіб створити список з n списків


97

Тому мені було цікаво, як найкраще створити список порожніх списків:

[[],[],[]...]

Через те, як Python працює зі списками в пам'яті, це не працює:

[[]]*n

Це створюється, [[],[],...]але кожен елемент - це той самий список:

d = [[]]*n
d[0].append(1)
#[[1],[1],...]

Щось на зразок розуміння списку працює:

d = [[] for x in xrange(0,n)]

Але для цього використовується віртуальна машина Python для циклу. Чи є спосіб використовувати мається на увазі цикл (скориставшись тим, що він написаний на C)?

d = []
map(lambda n: d.append([]),xrange(0,10))

Це насправді повільніше. :(


1
Я був би здивований, якщо є щось істотно швидше, ніж d = [[] for x in xrange(0,n)]. Вам або доведеться чітко циклічити в Python або викликати функцію / лямбда Python кілька разів (що має бути повільніше). Але все одно сподіваюся, що хтось опублікує щось, що свідчить про те, що я помиляюся :).
MAK

1
Коли ви вимірювали їх, з timeitчим ви дізналися?
S.Lott

Я щойно підтвердив, що map(lambda x: [], xrange(n))це повільніше, ніж розуміння списку.
Ендрю Кларк

Відповіді:


105

Мабуть, єдиний спосіб, який незначно швидший, ніж

d = [[] for x in xrange(n)]

є

from itertools import repeat
d = [[] for i in repeat(None, n)]

Не потрібно створювати новий intоб’єкт у кожній ітерації, і це на 15% швидше на моїй машині.

Редагувати : Використовуючи NumPy, ви можете уникнути використання циклу Python

d = numpy.empty((n, 0)).tolist()

але це насправді в 2,5 рази повільніше, ніж розуміння списку.


Як щодо map(lambda x:[], repeat(None,n))?
PaulMcG

3
@Paul: Це було б знову повільніше через виклик функції виклику лямбда-виразу.
Свен Марнах

Чи не слід це оновлювати зараз, коли діапазон відрізняється від Python 3?
beruic

@beruic Я просто цитую код із питання в першому блоці коду, тому насправді це не має сенсу змінювати.
Свен Марнах

12

Розуміння списку насправді реалізовується ефективніше, ніж явна циклічна робота (див . disВихідні дані, наприклад, функції ), і mapспосіб повинен викликати підступний об'єкт, що викликається, на кожній ітерації, що спричиняє значні накладні витрати.

Незалежно від того, [[] for _dummy in xrange(n)]це правильний спосіб зробити це, і жоден із крихітних (якщо вони взагалі існують) різниці швидкостей між різними іншими способами не повинен мати значення. Якщо, звичайно, ви не витрачаєте на це більшу частину свого часу - але в цьому випадку вам слід працювати над своїми алгоритмами. Як часто ви створюєте ці списки?


5
Будь ласка, ні, _як ім'я змінної! Інакше приємна відповідь :)
Свен Марнах

11
@Sven: Чому ні? Зазвичай він використовується для невикористаних змінних (якби його називали i, я для одного шукав би, де він використовується). Єдиною ловушкою було б те, що вона затінює _проведення останнього результату в REPL ... і це лише у випадку 2.x, де розуміння списку витікає.

Не дуже часто, саме тому я пішов вперед і використовував розуміння списку. Думав, що було б цікаво подивитися, що люди мають сказати. Я бачив, як Dropbox розмовляв у PyCon із використанням itertools.imap замість for циклу для оновлення хеду md5 в абсурдно великій кількості разів, і з тих пір я трохи одержимий циклами C.
munchybunch

12
Найважливіша причина не використовувати його - це те, що вона схиляє бентежити людей, змушуючи їх думати, що це якийсь особливий синтаксис. На додаток до конфлікту з _інтерактивним перекладачем, він також конфліктує із загальним псевдонімом гетексту. Якщо ви хочете уточнити, що змінна є фіктивною змінною, називайте її dummy, а не _.
Свен Марнах

12

Ось два методи, один солодкий і простий (і концептуальний), інший більш офіційний і може бути розширений у різних ситуаціях після прочитання набору даних.

Спосіб 1: Концептуальний

X2=[]
X1=[1,2,3]
X2.append(X1)
X3=[4,5,6]
X2.append(X3)
X2 thus has [[1,2,3],[4,5,6]] ie a list of lists. 

Спосіб 2: Формальний і розширюваний

Ще один елегантний спосіб збереження списку як списку списків різної кількості - який він читає з файлу. (У цьому файлі є поїзд набору даних) Поїзд - це набір даних із сказатими 50 рядків та 20 стовпців. тобто. Поїзд [0] дає мені 1-й рядок файлу csv, поїзд [1] дає мені 2-й рядок тощо. Мені цікаво відокремити набір даних 50 рядків як один список, за винятком стовпця 0, який є моєю поясненою змінною тут, тому його потрібно видалити з початкового набору даних поїздів, а потім змінити масштаб списку після списку, тобто списку списку . Ось код, який це робить.

Зауважте, що я читаю з "1" у внутрішньому циклі, оскільки мене цікавлять лише пояснювальні змінні. І я повторно ініціалізую X1 = [] в іншому циклі, інакше X2.append ([0: (len (поїзд [0]) - 1)]) "буде переписувати X1 знову і знову - до того ж вона більш ефективна пам'ять.

X2=[]
for j in range(0,len(train)):
    X1=[]
    for k in range(1,len(train[0])):
        txt2=train[j][k]
        X1.append(txt2)
    X2.append(X1[0:(len(train[0])-1)])

4

Для створення списку та списку списків використовуйте нижче синтаксис

     x = [[] for i in range(10)]

це створить 1-й список і ініціалізує його, поставивши число в [[число] і встановивши довжину списку, розміщену в діапазоні (довжина)

  • Для створення списку списків використовуйте нижче синтаксис.
    x = [[[0] for i in range(3)] for i in range(10)]

це ініціалізує список списків з розміром 10 * 3 та зі значенням 0

  • Для доступу / маніпулювання елементом
    x[1][5]=value

2

Тому я зробив кілька порівнянь швидкості, щоб досягти найшвидшого шляху. Ознайомлення зі списком справді дуже швидко. Єдиний спосіб наблизитись - це уникнути отримання байт-коду під час створення списку. Моєю першою спробою був наступний метод, який, здавалося б, в принципі швидший:

l = [[]]
for _ in range(n): l.extend(map(list,l))

(Звичайно, створюється список довжиною 2 ** n) Ця конструкція вдвічі повільніше, ніж розуміння списку, згідно timeit, як для коротких, так і для довгих (мільйон) списків.

Моєю другою спробою було використовувати starmap для виклику конструктора списку для мене. Є одна конструкція, яка, здається, працює з конструктором списку з максимальною швидкістю, але все-таки повільніше, але лише невеликою кількістю:

from itertools import starmap
l = list(starmap(list,[()]*(1<<n)))

Досить цікавий час виконання говорить про те, що саме виклик остаточного списку робить рішення стартової карти повільним, оскільки час його виконання майже точно дорівнює швидкості:

l = list([] for _ in range(1<<n))

Моя третя спроба виникла, коли я зрозумів, що список (()) також створює список, тому я спробував напевно простий:

l = list(map(list, [()]*(1<<n)))

але це було повільніше, ніж дзвінок зірки.

Висновок: для маніяків на швидкість: Використовуйте розуміння списку. Функції виклику виконуються лише в разі потреби. Використовуйте вбудовані.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.