Проверка равенства объектов после различных атрибутов - PullRequest
0 голосов
/ 01 февраля 2019

У меня есть два списка объектов, и мне нужно найти подходящие объекты в соответствии с двумя различными наборами атрибутов.Скажем, у меня есть объекты Vehicle (), и мне нужно сначала сопоставить все транспортные средства из первого списка, которые совпадают с транспортными средствами во втором, сначала посмотреть на соответствующие цвета, а затем на соответствующие бренды.У меня есть два решения, но я не уверен, что это лучшее, что я могу сделать.(Мне действительно нужно оптимизировать эту производительность)

Итак, скажем, у меня есть:

class Vehicle(object):
    def __init__(self, color, brand):
        self._color = color
        self._brand = brand

и списки объектов как таковые:

vehicles1= [Vehicle('blue','fiat'), Vehicle('red','volvo'), Vehicle('red','fiat')]

vehicles2 = [Vehicle('blue', 'volvo'), Vehicle('red', 'BMW')]

Первое решение, который кажется невероятно медленным, должен работать только с включением в список:

inersect_brand_wise = [x for x in vehicles1 for y in vehicles2 if x._brand == y._brand] 

затем

 intersect_color_wise = [x for x in vehicles1 for y in vehicles2 if x._color == y._color]

Второе решение, которое я нашел, состоит в разработке равенства:

class Vehicle(object):
    def __init__(self, color, brand):
        self._color = color
        self._brand = brand

    def __eq__(self, other):
        if isinstance(other, Vehicle):
            return self._brand == other._brand
        return False
    def __hash__(self):
        return hash((self._color, self._brand))

Теперь получить пересечение по марке тривиально:

inersect_brand_wise = [x for x in vehicles1 if x in vehicles2]

Чтобы получить пересечение по цвету, я сделал следующее:

class Car(Vehicle):
    def __init__(self, color, brand):
        Vehicle.__init__(self,color, brand)


def __hash__(self):
    return Vehicle.__hash__

def __eq__(self, other):
    if isinstance(other, Car):
        return other._color == self._color
    return False


def change_to_car(obj):
    obj.__class__ = Car
    return obj


cars1 = map(change_to_car, vehicles1)
cars2  = map(change_to_car, vehicles2)

И, таким образом,

intersect_color_wise = [x for x in cars1 if x in cars2]

дает второе пересечение.

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

Любые предложенияо том, как сделать лучше, чем все это?

Заранее спасибо, M

Ответы [ 4 ]

0 голосов
/ 01 февраля 2019

Вопрос: Проверка равенства объектов после различных атрибутов

Вместо того, чтобы впоследствии найти равных объектов, выполните loop in loop, проверьте равенство при создании экземпляра.
Для сохранения memory сохраняются только равные хеши объектов.

  • Используйте class attributs дляудерживайте dict из objects из set 1
    и list, чтобы удерживать _hash из равным objects.

    class VehicleDiff:
        ref1 = {}
        _intersection = []
    
        def __init__(self, set, color, brand):
            self.color = color
            self.brand = brand
    
  • Сохранить ссылку set 1 объектов в dict ref1.
    Проверить только объектов из set 2 против dict ref1 и сохранить только при равенстве .

            _hash = hash((color, brand))
            if set == 1:
                VehicleDiff.ref1[_hash] = self
    
            elif _hash in VehicleDiff.ref1:
                VehicleDiff._intersection.append(_hash)
    
  • Помощник methode intersection для получения VehicleDiff объекта из _hash.

        @staticmethod
        def intersection():
            print('intersection:{}'.format(VehicleDiff._intersection))
            for _hash in VehicleDiff._intersection:
                yield VehicleDiff.ref1[_hash]
    
  • Строковое представлениеVehicleDiff объекта.

        def __str__(self):
            return 'color:{}, brand:{}'.format(self.color, self.brand)
    
  • Существует , не нужно , чтобы удерживать объекты в list.

    Примечание : поскольку данные данного примера не имеют пересечения, я добавил ('red', 'fiat') к set 2

    for p in [('blue', 'fiat'), ('red', 'volvo'), ('red', 'fiat')]:
        VehicleDiff(1, *p)
    
    for p in [('blue', 'volvo'), ('red', 'BMW'), ('red', 'fiat')]:
        VehicleDiff(2, *p)
    
  • Напечатайте результат, , если есть .

    for vehicle in VehicleDiff.intersection():
        print('vehicle:{}'.format(vehicle))
    

Выход :

intersection:[2125945310]
vehicle:color:red, brand:fiat

Протестировано на Python: 3.4.2

0 голосов
/ 01 февраля 2019

Во-первых, насколько я могу судить, автомобили равны, если и марка, и цвет одинаковы.Обычно имеет смысл кодировать то, что имеет смысл (странная формулировка там):

class Vehicle(object):
    def __init__(self, color, brand):
        self.color = color #Why the underscores everywhere, e.g. _brand? those are usually indicative of something special-ish
        self.brand = brand

    def __eq__(self, other):
        #Ducktyping. If it quacks like a car...:
        return self.brand == other.brand and self.color == other.color

Пересечение двух множеств является «сложной» проблемой.Лучшее, что мы можем сделать, это то, что Python предлагает (ищите пересечение под set), а в худшем случае это кратное длине обоих наборов.Конечно, вы не можете использовать набор здесь.

В вашем случае, хотя, я думаю, что проблема проще, хотя.Не нужно форсировать понимание.Вы хотите классифицировать две вещи?сделайте это естественно, и это станет немного быстрее:

vcol = {'blue':set(), 'red':set() ... } #Or add in the loop below if list to long or uncertain
vbrand = {'fiat':set(), ... }

for v in cars1: 
    vcol[v.color].add(v)
    vbrand[v.brand].add(v)

А теперь просто составьте списки.Обратите внимание, что оператор in ниже O(1):

colorbuddies = []
brandbuddies = []
#Could be split into two comprehensions. Not sure two runs is worth it.
for v in cars2:
    #in operator depends on __eq__ defined above
    if v in vcol[v.color]: colorbuddies.append(v)
    if v in vbrand[v.brand]: brandbuddies.append(v)

Итак, в целом мы имеем линейную операцию, работающую над обоими списками!

0 голосов
/ 01 февраля 2019

На самом деле Python - это динамический язык.Это означает, что вы можете по своему усмотрению исправлять класс Vehicle, чтобы он соответствовал вашим потребностям.Вы готовите 2 других класса (я сделал их подклассами Vehicle, чтобы автозаполнение работало в IDE) с соответственно равенством бренда и цветовым равенством, и просто назначаете их членов в класс Vehicle:

class Vehicle(object):
    def __init__(self, color, brand):
        self._color = color
        self._brand = brand

class Vehicle_brand(Vehicle):
    def __eq__(self, other):
        return self._brand == other._brand
    def __hash__(self):
        return hash(self._brand)


class Vehicle_color(Vehicle):
    def __eq__(self, other):
        return self._color == other._color
    def __hash__(self):
        return hash(self._color)

Чтобы получить пересечение бренда:

Vehicle.__eq__ = Vehicle_brand.__eq__
Vehicle.__hash__ = Vehicle_brand.__hash__
intersect_brand_wise = [x for x in vehicles1 if x in vehicles2]

Затем, чтобы получить пересечение цветов:

Vehicle.__eq__ = Vehicle_color.__eq__
Vehicle.__hash__ = Vehicle_color.__hash__
intersect_color_wise = [x for x in vehicles1 if x in vehicles2]

Хорошая новость заключается в том, что если в вашем классе Vehicle есть другие члены, они остаются нетронутыми при измененииравенство, и вы никогда не копируете и не дублируете никакие объекты: только 2 метода в объектах класса.

Возможно, он не очень чистый, но должен работать ...

0 голосов
/ 01 февраля 2019

Как производительность в этом случае?Не располагайте полным набором данных для эмуляции производительности для правильного тестирования ...:

def get_intersections(list1, list2):
    brands, colors = map(set, zip(*[(v._brand, v._color) for v in list2]))
    inter_brands = [v for v in list1 if v._brand in brands]
    inter_colors = [v for v in list1 if v._colors in colors]
    return inter_brands, inter_colors

Вы также можете написать отдельные пересечения, если хотите:

from operator import attrgetter

def get_intersection(list1, list2, attr:str):
    getter = attrgetter(attr)
    t_set = {getter(v) for v in list2}
    results = [v for v in list1 if getter(v) in t_set]
    return results

# use it like this:
get_intersection(vehicles1, vehicles2, "_brand")

Вы можететакже масштабируйте первую функцию с помощью attrgetter, чтобы получить произвольное количество атрибутов:

def get_intersections(list1, list2, *attrs:str):
    getter = attrgetter(*attrs)
    if len(attrs) > 1:
        sets = list(map(set, zip(*[getter(v) for v in list2])))
    else:
        sets = [{getter(v) for v in list2}]
    results = {attr: [v for v in vehicles1 if getattr(v, attr) in sets[s]] for s, attr in enumerate(attrs)}
    return results

Тест:

>>> get_intersections(vehicles1, vehicles2, "_brand", "_color")

{'_brand': [<__main__.Vehicle object at 0x03588910>], '_color': [<__main__.Vehicle object at 0x035889D0>, <__main__.Vehicle object at 0x03588910>, <__main__.Vehicle object at 0x035889F0>]}

>>> get_intersections(vehicles1, vehicles2, "_brand")

{'_brand': [<__main__.Vehicle object at 0x03588910>]}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...