Лучшая практика для сравнения списков и извлечения некоторых данных - PullRequest
0 голосов
/ 19 июня 2020

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

Мне нужен наиболее эффективный способ для их сравнения и выбора писем, удаленных из старого списка.

список писем хранится в БД, поэтому я могу получать письма ids (электронные письма уникальны).

Код, который я использую:

old_ids_list = [1, 2, 3]
new_ids_list = [1, 2]
old_emails_list = ['toto@gmail.com', 'lolo@gmail.com', 'momo@gmail.com']
new_emails_list = ['toto@gmail.com', 'lolo@gmail.com',]

if len(old_ids_list) == len(new_ids_list) & len(set(old_ids_list) & set(new_ids_list)) == len(old_ids_list):
    pass
else:
    deleted = numpy.setdiff1d(old_emails_list, new_emails_list, assume_unique=False)

Это хорошая практика? или лучше использовать for loop? А почему?

Ответы [ 2 ]

2 голосов
/ 20 июня 2020

Во-первых, в состоянии if вы уже делаете тяжелую работу, поэтому нет необходимости в предварительном тестировании. Во-вторых, неясно, какова ваша отправная точка, если это идентификаторы или электронные письма или что-то еще, и ваши конечные точки.

Но кажется, что самым чистым подходом было бы использовать set полностью .

Я полагаю, вас устраивают идентификаторы (но тот же код будет работать для адресов электронной почты):

n = 3
a = set(range(1, n))  # *old* items
b = set(range(n - 1))  # *new* items


c = a - b  # items present in b but not in a (added)
# {0}
d = b - a  # items present in a but not in b (deleted)
# {2}

Теперь предположим, что отправной точкой является два list (снова идентификаторы или адрес электронной почты не имеют значения, я просто предполагаю идентификаторы для простоты), и позвольте нам предположить, что вы хотите знать как добавленные, так и удаленные элементы. Есть несколько возможных подходов:

def diffs_set(a, b):
    a = set(a)
    b = set(b)
    return a - b, b - a
def diffs_loop(a, b):
    a = set(a)
    b = set(b)
    return [x for x in a if x in b], [x for x in b if x in a]
def diffs_loop2(a, b):
    return [x for x in a if x in b], [x for x in b if x in a]
def diffs_np(a, b):
    return np.setdiff1d(a, b, assume_unique=True), np.setdiff1d(b, a, assume_unique=True)

чьи тайминги для некоторых размеров ввода выглядят следующим образом:

funcs = diffs_set, diffs_loop, diffs_loop2, diffs_np
for n in (10, 100, 1000, 10000):
    print(n)
    old_items = list(range(1, n))
    new_items = list(range(n - 1))
    for func in funcs:
        print(func.__name__)
        %timeit func(old_items, new_items)
    print()
# 10
# diffs_set
# The slowest run took 4.52 times longer than the fastest. This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 914 ns per loop
# diffs_loop
# 1000000 loops, best of 3: 1.97 µs per loop
# diffs_loop2
# 100000 loops, best of 3: 2.09 µs per loop
# diffs_np
# 10000 loops, best of 3: 65.6 µs per loop

# 100
# diffs_set
# 100000 loops, best of 3: 5.23 µs per loop
# diffs_loop
# 100000 loops, best of 3: 13.6 µs per loop
# diffs_loop2
# 10000 loops, best of 3: 116 µs per loop
# diffs_np
# The slowest run took 5.74 times longer than the fastest. This could mean that an intermediate result is being cached.
# 10000 loops, best of 3: 65.9 µs per loop

# 1000
# diffs_set
# 10000 loops, best of 3: 57.7 µs per loop
# diffs_loop
# 10000 loops, best of 3: 132 µs per loop
# diffs_loop2
# 100 loops, best of 3: 10.7 ms per loop
# diffs_np
# 1000 loops, best of 3: 374 µs per loop

# 10000
# diffs_set
# 1000 loops, best of 3: 818 µs per loop
# diffs_loop
# 1000 loops, best of 3: 1.6 ms per loop
# diffs_loop2
# 1 loop, best of 3: 1.06 s per loop
# diffs_np
# 100 loops, best of 3: 3.5 ms per loop

Наиболее важное замечание заключается в том, что при использовании наборов достигается самый быстрый и чистый подход. Следует отметить, что set s полезны даже для понимания list, потому что условие if становится O(1) (что приводит к общему значению O(n)) вместо O(n) (приводит к общему значению * 1027). *). Поскольку самой дорогой операцией является создание set в самом начале, вполне возможно, что понимание списка станет конкурентоспособным по сравнению с использованием только наборов, если необходимы только a - b или b - a, потому что тогда только один set() звонок необходим. Напротив, подход, основанный на NumPy, здесь неконкурентоспособен.

0 голосов
/ 20 июня 2020

Вы можете использовать понимание списка. Поскольку вы сказали, что электронные письма уникальны сами по себе, я не уверен, что вы хотите делать с идентификаторами, и нам не нужно преобразовывать в наборы (поправьте меня, если я ошибся).

deleted = [email for email in old_emails_list if email not in new_emails_list]

Плюсы этого подхода, imho, в том, что он хорошо читается и не требует внешнего пакета.

редактировать
Чтобы проверить, отличается ли новый список от старого списка, есть два szenar ios:
a) Если известно, что новый список подсписок старого списка, просто проверьте, есть ли какой-либо элемент в deleted, как вычислено выше.
b) Если в новом списке могут быть новые электронные письма, проверьте, содержит ли deleted какие-либо электронные письма, и если нет ' t, дополнительно проверьте наличие len(new_emails_list)==len(old_emails_list).

...