Python: почему множественные списочные выражения кажутся быстрее, чем одиночные для l oop с операторами if ... elif? - PullRequest
2 голосов
/ 24 апреля 2020

У меня есть немного кода, который я пытаюсь определить, есть ли более быстрый способ его запуска. По сути, у меня есть файл с разделителями, который я перебираю, чтобы найти набор флагов для анализа данных. Эти файлы могут быть очень длинными, поэтому я пытаюсь найти быстрый метод для этого.

Два метода, которые я пробовал, - это понимание списка и метод для l oop:

Метод 1:

flag_set_1 = [i for i,row in enumerate(data_file) if row[0] == flag_1]
flag_set_2 = [i for i,row in enumerate(data_file) if row[0] == flag_2]
flag_set_3 = [i for i,row in enumerate(data_file) if row[0] == flag_3]
flag_set_4 = [i for i,row in enumerate(data_file) if row[0] == flag_4]

Метод 2:

for i,row  in enumerate(data_file):
    if row[0] == flag_1:
        flag_set_1.append(i)
    elif row[0] == flag_2:
        flag_set_2.append(i)
    elif row[0] == flag_3:
        flag_set_3.append(i)
    elif row[0] == flag_4:
        flag_set_4.append(i)

Я действительно ожидал, что понимание списка в этом случае будет медленнее. Считая, что метод 1 должен будет повторяться по data_file 4 раза, а метод 2 - только один раз. Я подозреваю, что использование append () в методе 2 замедляет его.

Поэтому я спрашиваю, есть ли более быстрый способ реализовать это?

1 Ответ

0 голосов
/ 01 мая 2020

Без какой-либо выборки или эталона данных сложно воспроизвести ваши наблюдения. Я пытался с:

from random import randint
data_file = [[randint(0, 15) for _ in range(20)] for _ in range(100000)]
flag_1 = 1
flag_2 = 2
flag_3 = 3
flag_4 = 4

И обычный l oop был в два раза быстрее, чем четыре списка понимания (см. Тест ниже).

Если вы хотите улучшить скорость процесс, у вас есть несколько отведений.

Перечисления и обычные l oop

Если flag_n - строки, и вы уверены, что row[0] является одним из них для каждого row , тогда вы можете проверить один символ вместо всей строки. Например:

flag_1 = "first flag"
flag_2 = "second flag"
flag_3 = "third flag"
flag_4 = "fourth flag"

Посмотрите на вторые символы: f<I>rst, S<E>cond, T<H>ird, F<O>urth. Вы просто должны проверить, если row[0][1] == 'i' (или 'e' или 'h' или 'o') вместо row[0] == flag_n.

Regular l oop

Если вы хотите улучшить скорость обычного l oop, у вас есть несколько отведений.

Во всех случаях

Вы можете назначить flag = row[0] вместо получения row[0] первых элементов четыре раза. Это основа c, но она работает.

Если у вас есть информация о данных

Если данные отсортированы по флагу, вы, очевидно, можете сразу создать flag_n_set: найдите сначала последний flag_n и напишите flag_n_set = list(range(first_flag_n_index, last_flag_n_index+1)).

Если вы знаете частоту флагов, вы можете приказать if ... elif ... elif ... elif ... else сначала проверить более частый флаг, затем второй по частоте флаг, и т. д. c.

Вы также можете использовать диктовку, чтобы избежать последовательности if ... elif .... Если у вас не слишком много строк, которые не соответствуют ни одному флагу, вы можете использовать defaultdict:

from collections import defaultdict

def test_append_default_dict():
    flag_set = defaultdict(list)

    for i, row  in enumerate(data_file):
        flag_set[row[0]].append(i)

    return tuple(flag_set[f] for f in (flag_1, flag_2, flag_3, flag_4))

Тесты с данными выше:

test_list_comprehensions    3.8617278739984613
test_append                 1.9978336450003553
test_append_default_dict    1.4595633919998363
...