I have the following object that I would like to keep in a container that is sorted on insertion and does not contain duplicates, so I am using a SortedSet
from sortedcontainers import SortedSet, SortedList
class R():
    def __hash__(self):
        return hash(self.person_id)
    def __eq__(self, other):
        return self.__class__ == other.__class__ and self.person_id == other.person_id
    def __nq__(self, other):
        return not (self == other)
    def __lt__(self, other):
        return other.value < self.value
    def __init__(self, person_id, value):
        self.person_id = person_id
        self.value = value
    def __repr__(self):
        return "person: %s (%s)" % (self.person_id, self.value)
x = SortedSet()
x.add(R(13, 2))
x.add(R(17, 4))
x.add(R(11, 21))
x.add(R(7, -41))
print(x)
When I run this code I get the following output as expected:
SortedSet([person: 11 (21), person: 17 (4), person: 13 (2), person: 7 (-41)])
However if I added an extra duplicate element i.e. 17:
x.add(R(13, 2))
x.add(R(17, 4))
x.add(R(11, 21))
x.add(R(7, -41))
x.add(R(17, -67))
print(x)
I expect the R object with id 17 named person: 17 (4) to be moved to the back with value person: 17 (-67) like:
SortedSet([person: 11 (21), person: 13 (2), person: 7 (-41), person: 17 (-67)])
However nothing changes:
SortedSet([person: 11 (21), person: 17 (4), person: 13 (2), person: 7 (-41)])
How can I achieve the desired output as described using a SortedSet or any other container that is sorted on insertion and has no duplicates?
 
                        
DeepSpace's answer covers making this work (if somewhat inefficiently), but I'm going to issue a frame challenge here: This is a bad design.
Sets (the logical construct) are designed to store unique items. If something
added to a set is equal to something already in it, there's no reason to replace the old item, because the old item and the new item are equivalent. If your class does not use a definition of equality in which equality implies substitutability (two equal instances could be use interchangably in all relevant ways), then the instances aren't suitable for use in aset. Even withoutSortedSetgetting involved, using plainset, this wouldn't work, becauseset.addwon't replace an item when you insert an "equal" item; they're both equivalent after all, so why do the extra work?When you need to have a concept of keys that can map to values, where the values for a given key can be changed later without knowing the original value, you want a mapping (
dict-like), not a set (set-like).Kelly Bundy suggests that what you want may already exist in the
sortedcollectionspackage (ValueSortedDict), so I'd go with that if it works. Sincesortedcontainerscontains nothing that allows replacing values and sorting on the values, you'd have to go to a lot of work to add that behavior, roughly the same order of magnitude as implementing it yourself from scratch.Additional notes on why this doesn't work:
On top of your use case being fundamentally unsuited to sets (the logical concept, not merely
setitself),SortedSetitself is unusually inappropriate for your class, because implicitly relies on two invariants (only one of which is strictly required by Python, though the other is usually adhered to):__eq__should be consistent with__hash__: If two items are equal, they must have the same hash, and, as much as possible, two unequal items should not have the same hash (ideally the hash should be based on the same fields__eq__compares, but it's legal to base it on a subset of those fields)__eq__should be consistent with__lt__(and all other rich comparison operators): Ifa == b, thena < bandb < ashould both be false; similarly, ifa < borb < ais true, thena != b. Most sorting stuff in Python sticks to__lt__comparisons exclusively to allow inconsistent definitions, but if you stick the same objects in atuplefor comparisons, suddenly the lexicographic ordering rules meanstuple's own__lt__implementation relies on both the__lt__and__eq__of your class, so in practice you want them consistent anyway.Your class violates #2; the sorting rules are completely unrelated to the definition of equality.
SortedSetmuddles along here, determining uniqueness based on__hash__+__eq__and ordering with__lt__, but in certain circumstances (e.g. when removing elements) it relies on__lt__being consistent with__eq__. Specifically, after removing from the internalset(using__hash__+__eq___) it then removes from the internalSortedList, which bisects to find the element to remove, using__lt__, and confirms it found the right element with an equality check, using__eq__. Since__eq__and__lt__are inconsistent (they'd only match if you were trying to remove anRwith the sameperson_idandvalue), this never finds the value it's trying to remove, and it raises an exception.