2

I am using tuples as the key for a dictionary I created. For example:

example_dict = {}
example_dict[("A", "B")] = "1"

Later when I wish to modify the value of an entry in the dictionary I don't currently have control over the order of the tuple. For example:

("B", "A") may be the case, instead of ("A", "B")

I'm aware that these tuples are not equal from a simple == comparison that I tried in the python shell.

What I am wondering is how I could work around this? How could I make the following not produce a KeyError:

print (example_dict["B", "A"])

Is there a way to consistently order the elements of a tuple? Is there a way to ignore order completely? Any other work arounds? I'm aware I could just include all arrangements of the tuples as keys in the dictionary, and then collate the values of the different permutations later. I strongly want to avoid doing this as that only adds difficulty and complexity to the problem.

Chris Martin
  • 30,334
  • 10
  • 78
  • 137

3 Answers3

9

The usual ways are to either sort the keys:

example_dict[tuple(sorted(key_tuple))] = "1"

use frozensets as keys (if there won't be duplicate elements in the tuples):

example_dict[frozenset(key_tuple)] = "1"

or use frozensets of (item, count) tuples as keys (if there can be duplicate elements in the tuples):

example_dict[frozenset(Counter(key_tuple).viewitems())] = "1"

Whichever option you choose, you'll have to apply the same transformation when you look up values.

user2357112
  • 260,549
  • 28
  • 431
  • 505
8

You want your dictionary keys to be "sets" (a set is a collection for which an item is either in or not in the set, but that has no concept of order). Luckily python has what you need. Specifically because you need something hashable you want to use frozenset.

>>> example_dict = {}
>>> example_dict[frozenset(("A", "B"))] = "1"
>>> example_dict[frozenset(("B", "A"))]
'1'
>>> example_dict[frozenset(("A", "B"))]
'1'
CrazyCasta
  • 26,917
  • 4
  • 45
  • 72
  • 2
    Sets also have no concept of element multiplicity. That happens not to matter when your tuples all have length 2, but it can break messily when you have three elements and you can't distinguish `("A", "A", "B")` from `("A", "B", "B")`, or if your tuples can have different lengths and you can't distinguish `("A",)` from `("A", "A")`. It also breaks if you try to do something like `for a, b in example_dict`. – user2357112 Apr 20 '16 at 21:52
  • 1
    Any comment on the efficiency of this? It seems that `frozenset`s are much larger than `tuple`s, in terms of memory storage size. – sudo Jul 19 '17 at 19:22
  • @sudo they are bigger, but as the strings used increase in size so does the tuple, however frozen sets seem to remain size. https://www.codepile.net/pile/zRge1X0l – posdef Jun 03 '19 at 07:38
4

Instead of using a tuple, use a frozenset. A frozenset is just a constant set, just as a tuple can be thought of as a constant list.

Here's an example (from Python 3, but it will work in Python 2 as well):

>>> d = {}
>>> k1 = frozenset((1, 2))
>>> k2 = frozenset((2, 1))
>>> k1
frozenset({1, 2})
>>> k2
frozenset({1, 2})
>>> k1 == k2
True
>>> d[k1] = 123
>>> d[k2]
123
>>> 
Tom Karzes
  • 22,815
  • 2
  • 22
  • 41