3

I want to find any pair of elements in a list that have the same attribute. For example,

class X:
    def __init__(self, param):
        self.param = param

my_list = [X(1), X(2), X(3), X(2), X(3), X(3)]

So if comparing on x.param, I'd be looking for my_list[1], my_list[3] or my_list[2], my_list[4] or my_list[2], my_list[5] or my_list[4], my_list[5]. However, there's no guarantee that the list would necessary have any elements with the same property, e.g.

my_list = [X(1), X(2), X(3)]

might also be a valid parameter to this function.

The obvious way to do this seems to be:

def find_dupe(my_list, my_lambda):
    attrs = dict()
    for item in my_list:
        if my_lambda(item) in attrs:
             return [attrs[my_lambda(item)], item]
        attrs[my_lambda(item)] = item
    return []

But it seems a bit inelegant and I'm wondering if there's a nicer way to do this.

jpp
  • 159,742
  • 34
  • 281
  • 339
  • sounds like a job for [itertools.groupby()](https://stackoverflow.com/questions/773/how-do-i-use-pythons-itertools-groupby) - get all groups with > 1 element – Patrick Artner Jul 22 '18 at 11:29

1 Answers1

6

collections.defaultdict offers an O(n) solution to group objects by attribute:

from collections import defaultdict

class X:
    def __init__(self, param):
        self.param = param

my_list = [X(1), X(2), X(3), X(2), X(3), X(3)]

d = defaultdict(list)

for i in my_list:
    d[i.param].append(i)

The result indicates one object with param == 1, two objects with param == 2 and three objects with param == 3:

print(d)

defaultdict(list,
            {1: [<__main__.X at 0x855eb70>],
             2: [<__main__.X at 0x855e588>, <__main__.X at 0x856ae48>],
             3: [<__main__.X at 0x856af60>, <__main__.X at 0x856ad68>, <__main__.X at 0x856acf8>]})

To extract pairs of objects with the same attribute, you need only to filter items in the dictionary with a value with length greater than 1. Then use itertools.combinations to extract all combinations for these keys.

jpp
  • 159,742
  • 34
  • 281
  • 339