Избегание вложенных циклов при сравнении объектов в Python - PullRequest
2 голосов
/ 15 октября 2019

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

Скрипт 1 возвращает список Script1Object с каждым со своими свойствами. Аналогично, сценарий 2 возвращает список Script2Object с, которые похожи на Script1Object с, но не идентичны.

Мой код работает примерно так:

script1_list = script1()
script2_list = script2()

for item1 in script1_list:
    for item2 in script2_list:
        if is_match(item1, item2):
            do_matching_action()
            break
        elif is_different_match(item1, item2):
            do_other_matching_action()
            break

    if is_bad(item1):
       do_error_action() 

, где is_match() соответствует примерно десяти различным атрибутам. Например:

def is_match(item1, item2):
    return item1.name == item2.name and item1.ID == item2.number and item1.description.startswith(item2.desc)

и is_different_match() и is_bad() имеют аналогичный набор проверок.

Это отлично работает, но может быть медленным, особенно когда в первом списке может быть несколькосотни тысяч записей, а вторая легко имеет тысячи. Код также не очень модульный, поэтому я надеюсь, что избежание двойного цикла for улучшит расширяемость кода. Мне было интересно, как можно улучшить эту реализацию, поскольку я уверен, что это не лучшее использование возможностей Python.

Ответы [ 2 ]

2 голосов
/ 15 октября 2019

Учитывая, что совпадающие объекты должны иметь одинаковые идентификаторы (ID в Script1Object и number в Script2Object), и при условии, что все элементы script2_list имеют уникальные number s, вы можете создать отображениеот number до объекта, используя словарь . Затем, когда вы выполняете итерацию, вы можете получить Script2Object из словаря по его number напрямую (без необходимости циклически проходить по всему script2_list). Затем вы можете вызывать каждую функцию сопоставления на обоих объектах, как вы делали это раньше:

script1_list = script1()
script2_list = script2()
script2_dict = {obj.number: obj for obj in script2_list}

for item1 in script1_list:
    item2 = script2_dict.get(item1.ID, None)
    if item2 is None: 
        print('no item2 found for this id: ', item1.ID)
        # do something if there's no matching id
    if is_match(item1, item2):
        do_matching_action()
    elif is_different_match(item1, item2):
        do_other_matching_action()

    if is_bad(item1):
       do_error_action() 
1 голос
/ 15 октября 2019

Я думал в том же духе, что и @slider, но вам может понравиться мой подход. Он использует ту же логику первого поиска ID в списке из второго скрипта.

Основное отличие состоит в том, что я использовал списочное понимание, чтобы сделать код немного более компактным и избежать использования elif. Он также отслеживает различные совпадающие элементы в выходных списках, на случай, если вам понадобится доступ к ним позже.

class A(object):
    def __init__(self, name, ID, desc):
        self.name = name
        self.ID = ID
        self.desc = desc


class B(object):
    def __init__(self, name, ID, desc):
        self.name = name
        self.ID = ID
        self.desc = desc

def do_bad(a):
    print('catch some errors here')
    return a

def do_matching_action(a):
    print('Found match with name = {0}, ID  = {1}, and desc = {2}'.format(a.name, a.ID, a.desc))    
    return a      

item1 = A('name', 1, 'weird_desc')
item2 = A('name', 2, 'weird_desc')
item3 = A('name', 3, 'not_same_desc')
item4 = A('name', 4, 'wei')
item5 = B('name', 1, 'weird_desc')

list1 = [item1, item2, item1, item2, item5]
list2 = [item3, item4, item1, item4, item1]


id_dict = {a.ID: a for a in list2}
common_objects = [a for a in list1 if a.ID in id_dict.keys()]
uncommon_objects = [a for a in list1 if a.ID not in id_dict.keys()]

#peform matching tests
match1 = [do_matching_action(a) for a, b  in common_objects if (a.name == b.name) and (a.ID == b.ID) and a.desc.startswith(b.desc)]

#catch some errors
bad = [do_bad(a) for a in uncommon_objects]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...