Fastest way to remove duplicates in a list using each entries instance variables

Viewed 73

TL;DR

Is there a faster way to do

listOfClasses = [fooA, fooB, fooC]
setOfStrings = {c.string for c in listOfClasses}
newListOfClasses = []
for c in listOfClasses:
  if c.string in setOfStrings:
    newListOfClasses.append(c)
    setOfStrings.remove(c.string)

Restrictions/Caveats:

  • listOfClasses:

    • entries are not necessarily unique, may contain duplicate classes
    • number of entries usually in ballpark of 3-4, but can be as many as ~20
  • newListOfClasses:

    • cannot make new classes, it must use the same objects (the objects aren't mine, I don't know how they are initialized so I don't know whats inside them save for the string).
    • result must be at least iterable
    • result order does not matter
    • can overwrite original listOfClasses

Suppose I have a class

class Foo(object):
  def __init__(self,string):
    self.string = string

And a list of classes where I want to remove all the classes which have a duplicate "string" instance variable

fooA = Foo("alice")
fooB = Foo("alice")
fooC = Foo("His Royal Highness The Prince Philip, Duke of Edinburgh, Earl of Merioneth, Baron Greenwich, Royal Knight of the Most Noble Order of the Garter, Extra Knight of the Most Ancient and Most Noble Order of the Thistle, Member of the Order of Merit, Grand Master and First and Principal Knight Grand Cross of the Most Excellent Order of the British Empire, Knight of the Order of Australia, Additional Member of the Order of New Zealand, Extra Companion of the Queen’s Service Order, Royal Chief of the Order of Logohu, Extraordinary Companion of the Order of Canada, Extraordinary Commander of the Order of Military Merit, Lord of Her Majesty’s Most Honourable Privy Council, Privy Councillor of the Queen’s Privy Council for Canada, Personal Aide-de-Camp to Her Majesty, Lord High Admiral of the United Kingdom.")

listOfClasses = [fooA, fooB, fooC]

Here I want to remove either fooA or fooB (it does not matter which) such that I am left only with

listOfClasses = [fooB, fooC] # for example

So far I have the following:

setOfStrings = {c.string for c in listOfClasses}
newListOfClasses = []
for c in listOfClasses:
  if c.string in setOfStrings:
    newListOfClasses.append(c)
    setOfStrings.remove(c.string)

For the above I get the following timings:

# len(listOfClasses) = 3
2.22 ms ± 24.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# len(listOfClasses) = 20
2.29 ms ± 119 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
1 Answers

Utilizing the unique key of a dictionary in a dict comprehension should be pretty fast:

list({cls.string: cls for cls in listOfClasses}.values())

Full example:

class Foo(object):
    def __init__(self, string):
        self.string = string

fooA = Foo("alice")
fooB = Foo("alice")
fooC = Foo("His Royal Highness")

listOfClasses = [fooA, fooB, fooC]

print(list({cls.string: cls for cls in listOfClasses}.values()))
Related