The following class is a quick and dirty implementation with no guarantees for efficiency but it provides the desired behavior.
Should someone provide a better answer, I will gladly accept it!
class SortedValuesDict:
def __init__(self, args=None, **kwargs):
"""Initialize the SortedValuesDict with an Iterable or Mapping."""
from collections import Mapping
from sortedcontainers import SortedSet
self.__sorted_values = SortedSet()
self.__key_value_pairs = {}
self.__value_key_pairs = {}
if args is not None:
iterable = args.items() if isinstance(args, Mapping) else args
for key, value in iterable:
self.__setitem__(key, value)
for key, value in kwargs.items():
self.__setitem__(key, value)
def __repr__(self):
"""Get a representation of the SortedValuesDict."""
return f"SortedValuesDict({', '.join(f'{k}: {v}' for k, v in self)})"
def __setitem__(self, key, value):
"""Store the key, value pair in the SortedValuesDict."""
if key in self:
del self[key]
self.__key_value_pairs[key] = value
self.__sorted_values.add(value)
if value in self.__value_key_pairs:
self.__value_key_pairs[value].add(key)
else:
self.__value_key_pairs[value] = {key}
def __getitem__(self, key):
"""Get the value corresponding to the key."""
return self.__key_value_pairs[key]
def __delitem__(self, key):
"""Remove the given key from the SortedValuesDict"""
value = self.__key_value_pairs.pop(key)
keys = self.__value_key_pairs[value]
keys.remove(key)
if not keys:
del self.__value_key_pairs[value]
self.__sorted_values.remove(value)
def __contains__(self, key):
"""Test if key is in SortedValuesDict."""
return key in self.__key_value_pairs
def __iter__(self):
"""Iterate over the SortedValuesDict items."""
for value in self.__sorted_values:
for key in self.__value_key_pairs[value]:
yield key, value
def keys(self):
"""Iterate over the SortedValuesDict keys."""
for value in self.__sorted_values:
for key in self.__value_key_pairs[value]:
yield key
def values(self):
"""Iterate over the SortedValuesDict values."""
for value in self.__sorted_values:
for key in self.__value_key_pairs[value]:
yield value
def items(self):
"""Iterate over the SortedValuesDict items."""
yield from self
def peek(self, pos=0):
"""Peek at the next key for the value at position pos."""
return next(iter(self.__value_key_pairs[self.__sorted_values[pos]]))
def peekvalue(self, pos=0):
"""Peek at the value at position pos (default 0)."""
return self.__sorted_values[pos]
def peekitem(self, pos=0):
"""Peek at the the next key for the value at position pos."""
value = self.__sorted_values[pos]
return next(iter(self.__value_key_pairs[value])), value
def pop(self, pos=-1):
"""Pop a key corresponding to the value at position pos."""
value = self.__sorted_values[pos]
keys = self.__value_key_pairs[value]
key = keys.pop()
del self.__key_value_pairs[key]
if not keys:
del self.__value_key_pairs[self.__sorted_values.pop(pos)]
return key
def popitem(self, pos=-1):
"""Pop an item corresponding to the value at position pos."""
value = self.__sorted_values[pos]
keys = self.__value_key_pairs[value]
key = keys.pop()
del self.__key_value_pairs[key]
if not keys:
del self.__value_key_pairs[self.__sorted_values.pop(pos)]
return key, value
m = SortedValuesDict(a=3, b=1, c=2)
[*m.values()] # [1, 2, 3]
[*m] # [('b', 1), ('c', 2), ('a', 3)] (same as [*m.items()])
m.peekitem(0) # ('b', 1)
m.peekitem(-1) # ('a', 3)
m['d'] = 0
m.peekitem(0) # ('d', 0)
m.peekitem(-1) # ('a', 3)
m['e'] = 0
m # SortedValuesDict(d: 0, e: 0, b: 1, c: 2, a: 3)
m.peekitem(0) # (('d', 0), ('e', 0))
m.pop() # 'a' (from back)
m.pop(0) # 'd'
m # SortedValuesDict(e: 0, b: 1, c: 2)
m.popitem() # ('c', 2)
solved python mapping that stays sorted by value