This question already has answers here:
TypeError: unhashable type: 'dict', when dict used as a key for another dict [duplicate]
(2 answers)
Closed 6 years ago.
I was writing a basic uno-type card game, and wanted to be able to (while making 7 cards for the player) check if the value was already in the player's deck (I used random). I got an unhashable dict error, and some other questions were about the same error, but in different parts of code. Anyways, here's my code.
def CardGame():
nm=8
clist=["Red","Blue","Green","Yellow"]
nlist=[]
for i in range(0,10):
nlist.append(i)
pd={}
deck={"Red":0,"Red":1,"Red":2,"Red":3,"Red":4,"Red":5,"Red":6,"Red":7,"Red":8,"Red":9,"Blue":0,"Blue":1,"Blue":2,"Blue":3,"Blue":4,"Blue":5,"Blue":6,"Blue":7,"Blue":8,"Blue":9,"Green":0,"Green":1,"Green":2,"Green":3,"Green":4,"Green":5,"Green":6,"Green":7,"Green":8,"Green":9,"Yellow":0,"Yellow":1,"Yellow":2,"Yellow":3,"Yellow":4,"Yellow":5,"Yellow":6,"Yellow":7,"Yellow":8,"Yellow":9}
for i in range(1,nm):
c=random.choice(clist)
d=random.choice(nlist)
if ({c:d}) in deck:
pd.update({c:d})
del deck[c:d]
else:
nm=nm+1
print("%s %s"%(c,d))
With if ({c:d}) in deck:, you are checking if the dictionary {c:d} is present as a key in the dictionary deck. Dictionaries are unhashable (as mutable data types generally are), and a dictionary key must be hashable, so instead of just telling you "no," it throws the error you saw. A dictionary will never be present as a key in a dictionary.
Also, as noted in the comments, dictionary keys are unique, so the dictionary you made cannot exist as it appears. Consider a different data structure, like a list of tuples (e.g. [('Red', 1), ('Red', 2),...), or a dictionary with list values (e.g. {'Red':[1, 2, 3,...], 'Blue':[1, 2, 3,...],...}).
Your specific error comes from how you're trying to check for member ship in the dictionary. The in operator checks if a give key exists, but you're passing a one-element dictionary rather than just a key (you get that specific exception because dictionaries are not valid keys since they can't be hashed).
However, as Kevin commented above, you've also got a larger issue where your deck dictionary doesn't contain what you want it to contain, since you're reusing the same keys repeatedly. A dictionary can only have a single value for a given key (though the value could be a list or tuple, or some other type that contains additional items).
There are a variety of ways you can fix this issue. A set containing 2-tuples would work sort of like how you wanted your dict to work. You can add and remove elements, and efficiently check if a tuple is in the set or not with the in operator.
However, looking at what you're actually doing, I think a larger change to your algorithm will be much better. Rather than randomly picking a color and number and then checking if it is still in your deck, you should instead just select a random element from the collection directly. Rather than looping to select multiple values, use random.sample to select all the values at once. Or, if you're going to be taking more random values from the deck later, shuffle it and take a slice.
Here's a version of the code that uses random.shuffle on a list of 2-tuples, then slices off 10 of them from the end to become the pd list.
import itertools
import random
def CardGame():
nm=8
clist = ["Red","Blue","Green","Yellow"]
nlist = list(range(0,10)) # this is easier than looping to append the values
deck = list(itertools.product(clist, nlist)) # much easier than naming them all
random.shuffle(deck)
pd = deck[-10:] # slice 10 items from the end
del deck[-10:] # and then remove them from the list (fairly efficient at the end)
Related
So I have to create a simple program that accepts a database with a bunch of artists and their works of art with the details of the art. There is a given artist and I have to search through the database to find all the ones where they have to same artist and return them. I'm not allowed to use other built in functions nor import anything. Can someone tell me why its creating the error and what it means?
def works_by_artists(db,artists):
newlist = {}
for a in db.keys():
for b in db[artists]:
if a == b:
newlist.append(a);
return newlist
This prints out an error:
for b in db[artists]:
TypeError: unhashable type: 'list'
A dictionary can accept only some kinds of values as keys. In particular, they must be hashable, which mean they cannot change while in the dictionary ("immutable"), and there must be a function known to Python that takes the value and returns a nonnegative integer that somehow represents the integer. Integers, floats, strings, and tuples are immutable and hashable, while standard lists, dictionaries, and sets are not. The hash value of the key is used to store the key-value pair in the standard Python dictionary. This method was chosen for the sake of speed of looking up a key, but the speed comes at the cost of limiting the possible key values. Other ways could have been chosen, but this works well for the dictionary's intended purpose in Python.
You tried to execute the line for b in db[artists]: while the current value of artists was not hashable. This is an error. I cannot say what the value of artists was, since the value was a parameter to the function and you did not show the calling code.
So check which value of artists was given to function works_by_artists() that caused the displayed error. Whatever created that value is the actual error in your code.
This question already has answers here:
dict.keys()[0] on Python 3 [duplicate]
(3 answers)
Closed 6 years ago.
I just wanna make sure that in Python dictionaries there's no way to get just a key (with no specific quality or relation to a certain value) but doing iteration. As much as I found out you have to make a list of them by going through the whole dictionary in a loop. Something like this:
list_keys=[k for k in dic.keys()]
The thing is I just need an arbitrary key if the dictionary is not empty and don't care about the rest. I guess iterating over a long dictionary in addition to creation of a long list for just randomly getting a key is a whole lot overhead, isn't it?
Is there a better trick somebody can point out?
Thanks
A lot of the answers here produce a random key but the original question asked for an arbitrary key. There's quite a difference between those two. Randomness has a handful of mathematical/statistical guarantees.
Python dictionaries are not ordered in any meaningful way. So, yes, accessing an arbitrary key requires iteration. But for a single arbitrary key, we do not need to iterate the entire dictionary. The built-in functions next and iter are useful here:
key = next(iter(mapping))
The iter built-in creates an iterator over the keys in the mapping. The iteration order will be arbitrary. The next built-in returns the first item from the iterator. Iterating the whole mapping is not necessary for an arbitrary key.
If you're going to end up deleting the key from the mapping, you may instead use dict.popitem. Here's the docstring:
D.popitem() -> (k, v), remove and return some (key, value) pair as a 2-tuple;
but raise KeyError if D is empty.
You can use random.choice
rand_key = random.choice(dict.keys())
And this will only work in python 2.x, in python 3.x dict.keys returns an iterator, so you'll have to do cast it into a list -
rand_key = random.choice(list(dict.keys()))
So, for example -
import random
d = {'rand1':'hey there', 'rand2':'you love python, I know!', 'rand3' : 'python has a method for everything!'}
random.choice(list(d.keys()))
Output -
rand1
You are correct: there is not a way to get a random key from an ordinary dict without using iteration. Even solutions like random.choice must iterate through the dictionary in the background.
However you could use a sorted dict:
from sortedcontainers import SortedDict as sd
d = sd(dic)
i = random.randrange(len(d))
ran_key = d.iloc[i]
More here:.
http://www.grantjenks.com/docs/sortedcontainers/sorteddict.html
Note that whether or not using something like SortedDict will result in any efficiency gains is going to be entirely dependent upon the actual implementation. If you are creating a lot of SD objects, or adding new keys very often (which have to be sorted), and are only getting a random key occasionally in relation to those other two tasks, you are unlikely to see much of a performance gain.
How about something like this:
import random
arbitrary_key = random.choice( dic.keys() )
BTW, your use of a list comprehension there really makes no sense:
dic.keys() == [k for k in dic.keys()]
check the length of dictionary like this, this should do !!
import random
if len(yourdict) > 0:
randomKey = random.sample(yourdict,1)
print randomKey[0]
else:
do something
randomKey will return a list, as we have passed 1 so it will return list with 1 key and then get the key by using randomKey[0]
I am very new to python and my apologies is this has already been answered. I can see a lot of previous answers to 'sort' questions but my problem seems a little different from these questions and answers.
I have a list of keys, with each key contained in a tuple, that I am trying to sort. Each key is derived from a subset of the columns in a CSV file, but this subset is determined by the user at runtime and can't be hard coded as it will vary from execution to execution. I also have a datetime value that will always form part of the key as the last item in the tuple (so there will be at least one item to sort on - even if the user provides no additional items).
The tuples to be sorted look like:
(col0, col1, .... colN, datetime)
Where col0 to colN are based on the values found in columns in a CSV file, and the 'N' can change from run to run.
In each execution, the tuples in the list will always have the same number of items in each tuple. However, they need to be able to vary from run to run based on user input.
The sort looks like:
sorted(concurrencydict.keys(), key=itemgetter(0, 1, 2))
... when I do hard-code the sort based on the first three columns. The issue is that I don't know in advance of execution that 3 items will need to be sorted - it may be 1, 2, 3 or more.
I hope this description makes sense.
I haven't been able to think of how I can get itemgetter to accept a variable number of values.
Does anyone know whether there is an elegant way of performing a sort based on a variable number of items in python where the number of sort items is determined at run time (and not based on fixed column numbers or attribute names)?
I guess I'll turn my comment into an answer.
You can pass a variable number of arguments (which are packed into an iterable object) by using *args in the function call. In your specific case, you can put your user-supplied selection of column numbers to sort by into a sort_columns list or tuple, then call:
sorted_keys = sorted(concurrencydict.keys(), key=itemgetter(*sort_columns))
I wan't to create a program that select 2 random items from two different dictionaries. Now I wan't to check if the sum of those items is equal to the value provided by the User. And i wan't to perform this action until i find 2 random items from different dictionaries that add up to the number entered by the User.
Here is what i tried to do:
import random
credit=int(raw_input("Please enter your amount: "))
food=dict([(10, 'Lays'), (10,'Pepsi'), (10,'Burger')])
toys=dict([(10, 'Car'), (10,'Train'), (10,'Chess')])
ranf=random.choice(food.keys())
rant=random.choice(toys.keys())
while int(ranf)+int(rant)!=credit:
ranf=random.choice(food.keys())
rant=random.choice(toys.keys())
print(ranf)
print(food[ranf])
print(rant)
print(food[rant])
When i try to run this code it fails to print those two random items. I'm not getting any error message. Please run this code and help me out.
Thank You
The problem lies within the fact, that you create your dictionaries with duplicate keys - effectively, your food dictionary contains only (10,'Burger') and your toys dictionary has only (10,'Chess') item (they both contain only most recently added item, which replaced all the previous ones). The simplest and quickest fix would be to abandon the usage of a dictionary:
import random
credit=20
food=[(10, 'Lays'), (10,'Pepsi'), (10,'Burger')]
toys=[(10, 'Car'), (10,'Train'), (10,'Chess')]
ranf=random.choice(food)
rant=random.choice(toys)
while int(ranf[0])+int(rant[0])!=credit:
ranf=random.choice(food)
rant=random.choice(toys)
print(ranf)
print(rant)
food.keys() only returns unique keys. So, essentially the only list of keys returned by the food.keys() function is [10].
ex if you make a dictionary like food = dict([(10, 'Lays'), (15,'Pepsi'), (15,'Burger')])
then the list returned by food.keys() will be [10,15] and not [10,15,15] which is what you expect. So, in your code, if ranf = 10, then interpreter takes up the latest value assigned to that key.
Therefore, the random.choice() you are using goes in vain.
Also, there is a silly mistake in your code, you wrote print(food[rant]) instead of writing print(toys[rant]).
It would be better if you don't use a list, otherwise, make the keys different.
I have a Dictionary of Classes where the classes hold attributes that are lists of strings.
I made this function to find out the max number of items are in one of those lists for a particular person.
def find_max_var_amt(some_person) #pass in a patient id number, get back their max number of variables for a type of variable
max_vars=0
for key, value in patients[some_person].__dict__.items():
challenger=len(value)
if max_vars < challenger:
max_vars= challenger
return max_vars
What I want to do is rewrite it so that I do not have to use the .iteritems() function. This find_max_var_amt function works fine as is, but I am converting my code from using a dictionary to be a database using the dbm module, so typical dictionary functions will no longer work for me even though the syntax for assigning and accessing the key:value pairs will be the same. Thanks for your help!
Since dbm doesn't let you iterate over the values directly, you can iterate over the keys. To do so, you could modify your for loop to look like
for key in patients[some_person].__dict__:
value = patients[some_person].__dict__[key]
# then continue as before
I think a bigger issue, though, will be the fact that dbm only stores strings. So you won't be able to store the list directly in the database; you'll have to store a string representation of it. And that means that when you try to compute the length of the list, it won't be as simple as len(value); you'll have to develop some code to figure out the length of the list based on whatever string representation you use. It could just be as simple as len(the_string.split(',')), just be aware that you have to do it.
By the way, your existing function could be rewritten using a generator, like so:
def find_max_var_amt(some_person):
return max(len(value) for value in patients[some_person].__dict__.itervalues())
and if you did it that way, the change to iterating over keys would look like
def find_max_var_amt(some_person):
dct = patients[some_person].__dict__
return max(len(dct[key]) for key in dct)