How to check if a dictionary is invertible - python

I am working on a question that requires to point out the problem in a function that determines whether a dictionary is invertible (for every value appearing in the dictionary, there is only one key that maps to that value) or not. The question is below:
def is_invertible(adict):
inv_dict = make_inv_dict(adict)
return adict == inv_dict
def make_inv_dict(adict):
if len(adict) > 0:
key, val = adict.popitem()
adict = make_inv_dict(adict)
if val not in adict.values():
adict[key] = val
return adict
else:
return {}
Currently, this returns False for {'a': 'b', 'b': 'e', 'c': 'f'} when it is supposed to be True. I am sure that there is an issue in make_inv_dict function; is it simply because adict is not an appropriate variable name in adict = make_inv_dict(adict)? Or is there another reason why the function returns a wrong outcome?

At least three problems with the function you've given:
The condition adict == inv_dict checks whether the dictionary is its own inverse, not merely that it's invertible.
It uses pop_item to remove a key/value pair from the input dictionary, and then inserts it backwards, so the function operates in-place. By the time it's finished, adict's original contents will be completely destroyed, so the comparison will be meaningless anyway.
The line adict[key] = val inserts the key/value pair in the original order; the inverse order should be adict[val] = key. So this function doesn't do what its name promises, which is to make an inverse dictionary.
It should be noted that if not for the destruction of the dictionary (2.), the mistakes (1.) and (3.) would cancel out, because the outcome of the function is to rebuild the original dictionary but without duplicate values.
I'm guessing some people will find this question if they're looking for a correct way to invert a dictionary, so here is one: this function returns the inverse dictionary if it's possible, or None otherwise.
def invert_dict(d):
out = dict()
for k,v in dict.items():
if v in out:
return None
out[v] = k
return out
Helper function returning a boolean for whether a dictionary is invertible:
def is_invertible(d):
return invert_dict(d) is not None

My Answer:
def is_invertible(dict_var):
return len(dict_var.values()) == len(set(dict_var.values()))

Related

confused with python dictionary methods.values() .keys()

why this code isn't working? trying to get returns on items which value==key
L=[0,2,2,1,5,5,6,10]
x=dict(enumerate(L))
y=(filter(x.keys()==x.values(), x.items()))
print(list(y))
The keys() method returns a view of all of the keys.
The values() method returns a view of all of the values.
So, x.keys()==x.values() is asking whether all of the keys equal all of the values, which is of course not true.
Also, filter wants a function. But you're not passing it a function, you're just passing it the result of x.keys()==x.values(), or False. To turn that into a function, you'd need to use def or lambda to create a new function.
The function you want to create is a function that takes an item, and returns true if the key equals the value. Since an item is just a 2-element tuple with the key and value for that item, the function to check that is:
y = filter((lambda item: item[0] == item[1]), x.items())
Or, if that's a bit too confusing, don't try to write it inline; just def it separately:
def key_equals_value(item):
key, value = item
return key == value
y = filter(key_equals_value, x.items())
However, this is pretty clumsy; it's much easier to write it as a comprehension than a filter call:
y = ((key, value) for (key, value) in x.items() if key == value)
As a general rule, whenever you don't already have a function to pass to filter or map, and would have to create one with def or lambda, a comprehension will usually be more readable, because you can just write the expression directly.
And, if you want a list rather than a generator, you can do that with a comprehension just by changing the parens to square brackets:
y = [(key, value) for (key, value) in x.items() if key == value]
And, if you want just the values, not the key-value pairs:
y = [value for (key, value) in x.items() if key == value]
If you find yourself confused by comprehensions, they can always be converted into nested statements, with an append at the bottom. So, that last one is equivalent to:
y = []
for key, value in x.items():
if key == value:
y.append(value)
Also, you don't really need a dict here in the first place; you just want to iterate over the index, value pairs. So:
y = [value for (index, value) in enumerate(L) if index == value]

How to check a dictionary where values are lists for an element of that list?

If I have a dictionary where each value is a list, how can I check if there is a specific element in my list? For example:
myDict = { 0 : ['a','b','c'],
1 : ['d','e','f']}
How can I check if 'a' exists?
You can use any:
any('a' in lst for lst in myDict.values())
This will stop the iteration and evaluate to True on the first find. any is the built-in short-cut for the following pattern:
for x in y:
if condition:
return True
return False
# return any(condition for x in y)
It always strikes me as strange when someone wants to scan the values of a dictionary. It's highly unefficient if done many times.
Instead, I'd build another dictionary, or a set for quick check:
myDict = { 0 : ['a','b','c'],
1 : ['d','e','f']}
rset = {x for v in myDict.values() for x in v}
print(rset)
gives:
{'b', 'e', 'c', 'd', 'a', 'f'}
now:
'a' in rset
is super fast and concise. Build as many sets & dictionaries as you need on your original data set to get a fast lookup.
Check all values
We can use itertools.chain and use it in a rather self-explaining one liner:
from itertools import chain
if 'a' in chain.from_iterable(myDict.values()):
# do something
pass
Here we will chain the .values() of a list together in an iterable, and thus check membership of 'a'.
Note that this runs in linear time with the total number of values in the lists. In case you have to perform the membership check a single time, we can not do much about it, but in case we have to check it multiple times, it is better to cache the values in a set (given the values are hashable).
Check a specific key
In case you want to check a specific key, we can just lookup the corresponding value and check membership:
if 'a' in myDict[0]:
# do something
pass
In case it is not certain if the key is present in myDict, and we want to return False in that case, we can use .get(..) and use () (the empty tuple) as a fallback value:
# will not error, but False in case key does not exists
if 'a' in myDict.get(0, ()):
# do something
pass

How can I implement vice versa mapping in Python?

I have to convert a bunch of strings into numbers, process the numbers and convert back.
I thought of a map where I will add 2 keys when I've provided string:
Key1: (string, number);
Key2: (number, string).
But this is not optimal in terms of memory.
What I need to archieve in example:
my_cool_class.get('string') # outputs 1
my_cool_class.get(1) # outputs 'string'
Is there better way to do this in python?
Thanks in advance!
You can implement your own twoway dict like
class TwoWayDict(dict):
def __len__(self):
return dict.__len__(self) / 2
def __setitem__(self, key, value):
dict.__setitem__(self, key, value)
dict.__setitem__(self, value, key)
my_cool_class = TwoWayDict()
my_cool_class[1] = 'string'
print my_cool_class[1] # 'string'
print my_cool_class['string'] # 1
Instead of allocate another memory for the second dict, you can get the key from the value, consider that it will cost you with run-time.
mydict = {'george':16,'amber':19}
print (mydict.keys()[mydict.values().index(16)])
>>> 'george'
EDIT:
Notice that In Python 3, dict.values() (along with dict.keys() and dict.items()) returns a view, rather than a list. You therefore need to wrap your call to dict.values() in a call to list like so:
mydict = {'george':16,'amber':19}
print (list(mydict.keys())[list(mydict.values()).index(16)])
If optimal memory usage is an issue, you may not want to use Python in the first place. To solve your immediate problem, just add both the string and the number as keys to the dictionary. Remember that only a reference to the original objects will be stored. Additional copies will not be made:
d = {}
s = '123'
n = int(s)
d[s] = n
d[n] = s
Now you can access the value by the opposite key just like you wanted. This method has the advantage of O(1) lookup time.
You can create a dictionary of tuples this way you just need to check against the type of the variable to decide which one you should return.
Example:
class your_cool_class(object):
def __init__(self):
# example of dictionary
self.your_dictionary = {'3': ('3', 3), '4': ('4', 4)}
def get(self, numer):
is_string = isinstanceof(number, str)
number = str(number)
n = self.your_dictionary.get(number)
if n is not None:
return n[0] if is_string else n[1]
>>>> my_cool_class = your_cool_class()
>>>> my_cool_class.get(3)
>>>> '3'
>>>> my_cool_class.get('3')
>>>> 3

For loop inside while loop not working in Python

I am facing very unusual problem, below is code inside a class where pitnamebasename is 2d list.
For example:=
pitnamebasename= [['a','b'],['n','m'],['b','c'],['m','f'],['c','d'],['d',''],['h','f']]
Above list is not necessary to be in any order like ['d',''] can be at 0th order.
Here is my function (inside a class):-
def getRole(self,pitname):
basename=pitname
print '\n\n\nbasename=',basename
ind=True
while pitname and ind:
ind=False
basename=pitname
print 'basename=',basename
for i in self.pitnamebasename:
print 'comparing-',i[0],'and',pitname
if i[0] == pitname:
pitname=i[1]
print 'newpitname=',pitname
ind=True
break
print 'returning-',basename
return basename
pitname is the string for example here it can be 'a'. I want return value to be 'd' mean the traversing must be like a to b, b to c and d to None, hence return value must be d.
Please don't suggest me any other methods to solve.
Now my problem is that in the for loop its not looping till last but getting out in middle. Like return value is either b or c or even d depends on what I am searching. Actually list is very very long. Strange thing I noted that for loop loops only to that index where it loops till its first time. Like here first time for loop gets end when it find 'a' and pitname becomes 'b' but when it search for 'b' it loops till it find 'a' only. Does anyone knows how it is happening?
pitnamebasename= [['a','b'],['n','m'],['b','c'],['m','f'],['c','d'],['d',''],['h','f']]
First, change your '2d' array into a dict:
pitnamebasename = dict(pitnamebasename)
Now, it should be a simple matter of walking from element to element, using the value associated with the current key as the next key, until the value is the empty string; then
return the current key. If pitname ever fails to exist as a key, it's treated as if it does exist and maps to the empty string.
def getRole(self, pitname):
while pitnamebasename.get('pitname', '') != '':
pitname = pitnamebasename[pitname]
return pitname
A defaultdict could also be used in this case:
import collections.defaultdict
pitnamebasename = defaultdict(str, pitnamebasename)
def getRole(self, pitname):
while pitnamebasename[pitname] != '':
pitname = pitnamebasename[pitname]
return pitname
You asked for the solution to your problem but I am having trouble replicating the problem. This code does the same thing without requiring you to change your entire class storage system.
By converting your list of lists into a dictionary lookup (looks like the following)
as_list_of_lists = [['a','b'],['n','m'],['b','c'],['m','f'],['c','d'],['d',''],['h','f']]
as_dict = dict(as_list_of_lists)
# as_dict = {'a': 'b', 'c': 'd', 'b': 'c', 'd': '', 'h': 'f', 'm': 'f', 'n': 'm'}
we can do a default dictionary lookup using the dictionary method .get. This will look for an item (say we pass it 'a') and return the associated value ('b'). If we look for something that isn't in the dictionary, .get will return the second value (a default value) which we can supply as ''. Hence as_dict.get('z','') will return ''
class this_class
def __init__(self):
self.pitnamebasename= [['a','b'],['n','m'],['b','c'],['m','f'],['c','d'],['d',''],['h','f']]
def getRole(self,pitname):
lookup = dict(self.pitnamebasename)
new_pitname = pitname
while new_pitname != '':
pitname = new_pitname
new_pitname = lookup.get(pitname, '')
return pitname

Python: Elements of dictionary

def big(dict, n):
line = []
for k in dict:
if k > n:
line.append(k)
return line
I have to find all the elements in dict larger than n.
However, my code only returns the largest number in dict larger than n.
What do I need to do in order to make it correct?
The return line is tabbed too far over, so it returns when the first key larger than n is found (Note: a dictionary isn't ordered by the way you write it), rather than going over all keys before returning. Try:
def big(dic, n):
line = []
for k in dic:
if k > n:
line.append(k)
return line
In fact, you might prefer it to use list comprehension (and the function becomes just one line).
def big(dic, n):
return [k for k in dic if k>n]
.
Dictionaries compomise of key value pairs, {key: value} and when we iterate over a dictionary we are iterating over it's keys. This explains the use of the variable k to iterate over the keys. That is,
[k for k in dic] = [key1, key2, ...]
Hence, if you want to find that with the largest value in the dictionary, you can use:
return [dic[k] for k in dic if dic[k]>n]
Note: I've changed the variable name to dic since (as #AndrewJaffe mentions) dict is a built-in object, and renaming it here may cause unexpected things to occur, and is generally considered bad practise. For example, if you wanted to check type(dic)==dict.
Naively iterating over a dictionary gives you a sequence of keys. not values.
So to do what you want, you need itervalues:
for k in d.itervalues(): ### call it "d" rather than "dict"
if k>n:
line.append(k)
Or, as others have pointed out, use a list comprehension.
Also, don't use dict for the name, as it shadows a builtin.
def big(dic, n):
line = []
for k in dic:
if dic[k]> n: #compare value instead of key
line.append(k) #use k if you're appending key else dic[k] for val
return line
output:
>>> print big({'a':10,'b':15, 'c':12},11)
['c']
move the return statement backwards two tabs otherwise it will return on the first value larger than n.

Categories

Resources