how to ignore the order of elements in a tuple - python

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.

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.

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'

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
>>>

Related

How to sort a list containing frozensets (python)

I have a list of frozensets that I'd like to sort, Each of the frozensets contains a single integer value that results from an intersection operation between two sets:
k = frozenset(w) & frozenset(string.digits)
d[k] = w # w is the value
list(d) # sorted(d) doesn't work since the keys are sets and sets are unordered.
Here is the printed list:
[frozenset({'2'}), frozenset({'1'}), frozenset({'4'}), frozenset({'3'})]
How can I sort the list using the values contained in the sets?
You need to provide function as key to sorted which would accept frozenset as argument and return something which might be compared. If each frozenset has exactly 1 element and said element is always single digit then you might use max function (it will extract that single element, as sole element is always biggest element of frozenset) that is
d1 = [frozenset({'2'}), frozenset({'1'}), frozenset({'4'}), frozenset({'3'})]
d2 = sorted(d1,key=max)
print(d2)
output
[frozenset({'1'}), frozenset({'2'}), frozenset({'3'}), frozenset({'4'})]
If you want to know more read Sorting HOW TO
Previous answers can not sorted correctly, Because of strings
d = [frozenset({'224'}), frozenset({'346'}), frozenset({'2'}), frozenset({'22345'})]
sorted(d, key=lambda x: int(list(x)[0]))
Output:
[frozenset({'2'}),
frozenset({'224'}),
frozenset({'346'}),
frozenset({'22345'})]
Honestly, unless you really need to keep the elements as frozenset, the best might be to generate a list of values upstream ([2, 1, 4, 3]).
Anyway, to be able to sort the frozensets you need to make them ordered elements, for instance by converting to tuple. You can do this transparently using the key parameter of sorted
l = [frozenset({'2'}), frozenset({'1'}), frozenset({'4'}), frozenset({'3'})]
sorted(l, key=tuple)
or natsorted for strings with multiple digits:
from natsort import natsorted
l = [frozenset({'2'}), frozenset({'1'}), frozenset({'14'}), frozenset({'3'})]
natsorted(l, key=tuple)
output:
[frozenset({'1'}), frozenset({'2'}), frozenset({'3'}), frozenset({'14'})]

How to unpack dict with one key-value pair to two variables more elegantly? [duplicate]

This question already has answers here:
How to extract dictionary single key-value pair in variables
(11 answers)
Closed 3 years ago.
Currently, I'm using this:
d = {'a': 'xyz'}
k, v = list(*d.items())
The starred expression is required here, as omitting it causes the list function/constructor to return a list with a single tuple, which contains the key and value.
However, I was wondering if there were a better way to do it.
Keep nesting:
>>> d = {'a': 'xyz'}
>>> ((k,v),) = d.items()
>>> k
'a'
>>> v
'xyz'
Or equivalently:
>>> (k,v), = d.items()
>>> k
'a'
>>> v
'xyz'
>>>
Not sure which I prefer, the last one might be a bit difficult to read if I was glancing at it.
Note, the advantage here is that it is non-destructive and fails if the dict has more than one key-value pair.
Since dict items do not support indexed access, you might resort to the following non-mutating retrieval of the first (and only) item:
k, v = next(iter(d.items()))
This has the advantage of not only working for dicts of any size, but remaining an O(1) operation which other solutions that unpack the items or convert them to a list would not.
If you don't mind the dictionary getting altered, this'll do:
k, v = d.popitem()

python - dictionary with set of tuples as value is not created correctly

I have read this but it does not seem to work for me -
>>> dict = {}
>>> dict[ "a" ] = set(("1111","test1"))
>>> dict
{'a': set(['1111', 'test1'])}
>>> dict[ "a" ].add( ("2222","test2"))
>>> dict
{'a': set(['1111', 'test1', ('2222', 'test2')])}
I was expecting also the first one to look like a tuple - why is that splitted?
I want when doing :
>>> for key,val_set in dict.items() :
... for val in val_set :
... print val
...
1111
test1
('2222', 'test2')
to have 2 items printed not three.
set() takes the values from an iterable and adds each value separately. You passed in a tuple, which is an iterable with two values.
If you wanted to add the tuple itself, you need to wrap that in an iterable, like a list:
set([("1111","test1")])
In Python 3, you can instead use a set display to create a new set:
{("1111","test1")}
A set display takes elements separated by commas, and ("1111", "test1") is one tuple element here.
In the other post, the set was created with a set display (so {tuple1, tuple2, etc.}) and the answers used either an itertools function or a generator expression to produce the sequence of tuples for the set() function to process; a both produce an iterable type.
You have got your answer but I will advice you to use defaultdict instead. All these type of cases like this set([("1111","test1")]) are properly handled. You don't have to worry about initialize too.
Try This.
from collections import defaultdict
s = defaultdict(set)
s["a"].add(("pw", "sq"));
s["a"].add(("wq", "qq"));
s["a"].add(("aa", "bb"));
for k, v in s.items():
print (k)
for g in v:
print (g)
And please note this, I have assumed all your values opposite your keys are sets.
if this is not the case, go with the first one.

How to fetch the key/value pair of a dictionary only containing one item?

Let's say I have dict. I don't know the key/value inside. How do I get this key and the value without doing a for loop (there is only one item in the dict).
You might wonder why I am using a dictionary in that case. I have dictionaries all over my API and I don't want the user to be lost. It's only a matter of consistency. Otherwise, I would have used a list and indexes.
Use the proper data type for the job. Your goal should be to have workable code, not that you use the same data type all over the place.
If your dictionary only contains one key and one value, you can get either with indexing:
key = list(d)[0]
value = list(d.values())[0]
or to get both:
key, value = list(d.items())[0]
The list calls are needed because in Python 3, .keys(), .values() and .items() return dict views, not lists.
Another option is to use sequence unpacking:
key, = d
value, = d.values()
or for both at the same time:
(key, value), = d.items()
Just get the first item in the dictionary using an iterator
>>> d = {"foo":"bar"}
>>> k, v = next(iter(d.items()))
>>> k
'foo'
>>> v
'bar'
You can do this:
>>> d={1:'one'}
>>> k=list(d)[0]
>>> v=d[k]
Works in Python 2 or 3
d.popitem() will give you a key,value tuple.

Efficient way to either create a list, or append to it if one already exists?

I'm going through a whole bunch of tuples with a many-to-many correlation, and I want to make a dictionary where each b of (a,b) has a list of all the a's that correspond to a b. It seems awkward to test for a list at key b in the dictionary, then look for an a, then append a if it's not already there, every single time through the tuple digesting loop; but I haven't found a better way yet. Does one exist? Is there some other way to do this that's a lot prettier?
See the docs for the setdefault() method:
setdefault(key[, default])
If key is
in the dictionary, return its value.
If not, insert key with a value of
default and return default. default
defaults to None.
You can use this as a single call that will get b if it exists, or set b to an empty list if it doesn't already exist - and either way, return b:
>>> key = 'b'
>>> val = 'a'
>>> print d
{}
>>> d.setdefault(key, []).append(val)
>>> print d
{'b': ['a']}
>>> d.setdefault(key, []).append('zee')
>>> print d
{'b': ['a', 'zee']}
Combine this with a simple "not in" check and you've done what you're after in three lines:
>>> b = d.setdefault('b', [])
>>> if val not in b:
... b.append(val)
...
>>> print d
{'b': ['a', 'zee', 'c']}
Assuming you're not really tied to lists, defaultdict and set are quite handy.
import collections
d = collections.defaultdict(set)
for a, b in mappings:
d[b].add(a)
If you really want lists instead of sets, you could follow this with a
for k, v in d.iteritems():
d[k] = list(v)
And if you really want a dict instead of a defaultdict, you can say
d = dict(d)
I don't really see any reason you'd want to, though.
Use collections.defaultdict
your_dict = defaultdict(list)
for (a,b) in your_list:
your_dict[b].append(a)
you can sort your tuples O(n log n) then create your dictionary O(n)
or simplier O(n) but could impose heavy load on memory in case of many tuples:
your_dict = {}
for (a,b) in your_list:
if b in your_dict:
your_dict[b].append(a)
else:
your_dict[b]=[a]
Hmm it's pretty much the same as you've described. What's awkward about that?
You could also consider using an sql database to do the dirty work.
Instead of using an if, AFAIK it is more pythonic to use a try block instead.
your_list=[('a',1),('a',3),('b',1),('f',1),('a',2),('z',1)]
your_dict={}
for (a,b) in your_list:
try:
your_dict[b].append(a)
except KeyError:
your_dict[b]=[a]
print your_dict
I am not sure how you will get out of the key test, but once they key/value pair has been initialized it is easy :)
d = {}
if 'b' not in d:
d['b'] = set()
d['b'].add('a')
The set will ensure that only 1 of 'a' is in the collection. You need to do the initial 'b' check though to make sure the key/value exist.
Dict get method?
It returns the value of my_dict[some_key] if some_key is in the dictionary, and if not - returns some default value ([] in the example below):
my_dict[some_key] = my_dict.get(some_key, []).append(something_else)
There's another way that's rather efficient (though maybe not as efficient as sets) and simple. It's similar in practice to defaultdict but does not require an additional import.
Granted that you have a dict with empty (None) keys, it means you also create the dict keys somewhere. You can do so with the dict.fromkeys method, and this method also allows for setting a default value to all keys.
keylist = ['key1', 'key2']
result = dict.fromkeys(keylist, [])
where result will be:
{'key1': [], 'key2': []}
Then you can do your loop and use result['key1'].append(..) directly

Categories

Resources