Comprehensions with multiple input sets - python

I'm experimenting with python and am stuck trying to understand the error messages in the context of what I am doing.
I'm playing around with comprehensions and trying to find a pattern to create a list/dictionary comprehension with more than one input set (assuming this is possible):
Note: Here the word input set means the input area of the comprehension. In setbuilder notation, from where python derived its comprehensions [Y for X in LIST], Y is the output function, X is the variable and LIST is the input set.
Assume I have the following working code:
from random import randint
mydict = {k: 0 for k in range(10)}
result = {randint(0,9): v + 1 for v in mydict.values()}
I'm not trying to do anything special about it. This is not even useful code because it won't work as expected. All elements in the dictionary will have the value 1, instead of just those pointed at by the random generator. My only objective is to have a basis from where I start my attempt at working with a tuple of input sets.
from random import randint
mydict = {k: 0 for k in range(10)}
result = {k: v + 1 for k, v in (randint(0,9), mydict.values())}
This option gives me: TypeError: 'int' object is not iterable.
By swapping the input sets and unpacking I have:
result = {k: v + 1 for *v, k in (mydict.values(), randint(0,9))}
But this option gives me: TypeError: can only concatenate list (not "int") to list
Are these errors appearing because I am trying to do something the language grammar does not understand, or am I missing something and I could in fact fix the code?

You will have to create a separate comprehension for the random numbers, as it currently stands, you have only one random number. Also, you will then need to zip the results to get a combined entity:
>>> from random import randint
>>> mydict = {k: 0 for k in range(10)}
>>> result = {k: v + 1 for k, v in zip([randint(0,9) for _ in range(10)] , mydict.values())}
>>> result
{2: 1, 3: 1, 4: 1, 5: 1, 8: 1, 9: 1}
Note that since your initial dict has the value 0 for all its keys, all the values in the result dict are 1 (0+1).
Also, since we are making the keys random, there can be possible overlaps (say 2 was generated twice), so that's why we don't see all the keys in the result dictionary.
As #wim notes in comments below, a better way to generate this result dictionary would be to use:
>>> {randint(0,9): v+1 for v in mydict.values()}
{0: 1, 1: 1, 2: 1, 3: 1, 6: 1, 7: 1}

Related

typeerror: unhashable type: 'list' ,How to count occurrence of elements in python list and save it in dictionary?

d = {}
x = []
l=[1,1,1,2,3,3,5,5]
def count(lst,r):
z = 0
for i in lst:
if i==r:
z+=1
return z
print(count(l,1))
def conv_to_dict(lst):
d={}
d={x:count(lst,i) for i in set(lst)}
print(conv_to_dict(l))
the error as follows:
I want to convert the list into a dictionary in which values is number of times its key appears in the list
<ipython-input-32-a4e2a975b11c> in <dictcomp>(.0)
13 d={}
14 print(count(lst,lst[4]))
---> 15 d={x:count(lst,i) for i in set(lst)}
16
17 print(conv_to_dict(l))
TypeError: unhashable type: 'list'
You're getting that error because you're using x (which is defined in the caller's scope as an empty list) as the key. A list can't be used as a dictionary key because lists are mutable and keys must be immutable. That aside, though, you don't want a list to be the key (and certainly not x which is just an empty list), but rather the specific item that you counted, which would be i.
An easier solution is to use the Counter class:
>>> from collections import Counter
>>> Counter(l)
Counter({1: 3, 3: 2, 5: 2, 2: 1})
Variable x is of type list and cannot be used as dictionary keys. You probably meant i variable as the key:
l=[1,1,1,2,3,3,5,5]
def count(lst,r):
z = 0
for i in lst:
if i==r:
z+=1
return z
def conv_to_dict(lst):
d={i:count(lst,i) for i in set(lst)}
return d
print(conv_to_dict(l))
Prints:
{1: 3, 2: 1, 3: 2, 5: 2}
Note: There's collections.Counter for that, as stated in #Samwise's answer

Python dictionary - list compute to avergae

I have a dictionary with a list as value.
I want to have an average of this list.
How do I compute that?
dict1 = {
'Monty Python and the Holy Grail': [[9, 10, 9.5, 8.5, 3, 7.5, 8]],
"Monty Python's Life of Brian": [[10, 10, 0, 9, 1, 8, 7.5, 8, 6, 9]],
"Monty Python's Meaning of Life": [[7, 6, 5]],
'And Now For Something Completely Different': [[6, 5, 6, 6]]
}
I have tried
dict2 = {}
for key in dict1:
dict2[key] = sum(dict1[key])
but it says: "TypeError: unsupported operand type(s) for +: 'int' and 'list'"
As noted in other posts, the first issue is that your dictionary keys are lists of lists, and not simple lists. The second issue is that you were calling sum, without then dividing by the number of elements, which would not give you an average.
If you are willing to use numpy, try this:
import numpy as np
dict_of_means = {k:np.mean(v) for k,v in dict1.items()}
>>> dict_of_means
{'Monty Python and the Holy Grail': 7.9285714285714288, "Monty Python's Life of Brian": 6.8499999999999996, "Monty Python's Meaning of Life": 6.0, 'And Now For Something Completely Different': 5.75}
Or, without using numpy or any external packages, you can do it manually by first flattening your lists of lists in the keys, and going through the same type of dict comprehension, but getting the sum of your flattened list and then dividing by the number of elements in that flattened list:
dict_of_means = {k: sum([i for x in v for i in x])/len([i for x in v for i in x])
for k, v in dict1.items()}
Note that [i for x in v for i in x] takes a list of lists v and flattens it to a simple list.
FYI, the dictionary comprehension syntax is more or less equivalent to this for loop:
dict_of_means = {}
for k,v in dict1.items():
dict_of_means[k] = sum([i for x in v for i in x])/len([i for x in v for i in x])
There is an in-depth description of dictionary comprehensions in the question I linked above.
If you don't want to use external libraries and you want to keep that structure:
dict2 = {}
for key in dict1:
dict2[key] = sum(dict1[key][0])/len(dict1[key][0])
The problem is that your values are not 1D lists, they're 2D lists. If you simply remove the extra brackets, your solution should work.
Also don't forget to divide the sum of the list by the length of the list (and if you're using python 2, to import the new division).
You can do that simply by using itertools.chain and a helper function to compute average.
Here is the helper function to compute average
def average(iterable):
sum = 0.0
count = 0
for v in iterable:
sum += v
count += 1
if count > 0:
return sum / count
If you want to average for each key, you can simply do that using dictionary comprehension and helper function we wrote above:
from itertools import chain
averages = {k: average(chain.from_iterable(v)) for k, v in dict1.items()}
Or If you want to get average across all the keys:
from itertools import chain
average(chain.from_iterable(chain.from_iterable(dict1.values())))
Your lists are nested, all being lists of a single item, which is itself a list of the actual numbers. Here I extract these lists using val[0], val being the outer lists:
for key, val in dict1.copy().items():
the_list = val[0]
dict1[key] = sum(the_list)/len(the_list)
This replaces all these nested lists with the average you are after. Also, you should never mutate anything while looping over it. Therefore, a copy of the dict is used above.
Alternatively you could make use of the fancier dictionary comprehension:
dict2 = {key: sum(the_list)/len(the_list) for key, (the_list,) in dict1.items()}
Note the clever but subtle way the inner list is extracted here.

How do I access values from dictionary randomly in python3? [duplicate]

How can I get a random pair from a dict? I'm making a game where you need to guess a capital of a country and I need questions to appear randomly.
The dict looks like {'VENEZUELA':'CARACAS'}
How can I do this?
One way would be:
import random
d = {'VENEZUELA':'CARACAS', 'CANADA':'OTTAWA'}
random.choice(list(d.values()))
EDIT: The question was changed a couple years after the original post, and now asks for a pair, rather than a single item. The final line should now be:
country, capital = random.choice(list(d.items()))
I wrote this trying to solve the same problem:
https://github.com/robtandy/randomdict
It has O(1) random access to keys, values, and items.
If you don't want to use the random module, you can also try popitem():
>> d = {'a': 1, 'b': 5, 'c': 7}
>>> d.popitem()
('a', 1)
>>> d
{'c': 7, 'b': 5}
>>> d.popitem()
('c', 7)
Since the dict doesn't preserve order, by using popitem you get items in an arbitrary (but not strictly random) order from it.
Also keep in mind that popitem removes the key-value pair from dictionary, as stated in the docs.
popitem() is useful to destructively iterate over a dictionary
>>> import random
>>> d = dict(Venezuela = 1, Spain = 2, USA = 3, Italy = 4)
>>> random.choice(d.keys())
'Venezuela'
>>> random.choice(d.keys())
'USA'
By calling random.choice on the keys of the dictionary (the countries).
Try this:
import random
a = dict(....) # a is some dictionary
random_key = random.sample(a, 1)[0]
This definitely works.
This works in Python 2 and Python 3:
A random key:
random.choice(list(d.keys()))
A random value
random.choice(list(d.values()))
A random key and value
random.choice(list(d.items()))
Since the original post wanted the pair:
import random
d = {'VENEZUELA':'CARACAS', 'CANADA':'TORONTO'}
country, capital = random.choice(list(d.items()))
(python 3 style)
If you don't want to use random.choice() you can try this way:
>>> list(myDictionary)[i]
'VENEZUELA'
>>> myDictionary = {'VENEZUELA':'CARACAS', 'IRAN' : 'TEHRAN'}
>>> import random
>>> i = random.randint(0, len(myDictionary) - 1)
>>> myDictionary[list(myDictionary)[i]]
'TEHRAN'
>>> list(myDictionary)[i]
'IRAN'
When they ask for a random pair here they mean a key and value.
For such a dict where the key:values are country:city,
use random.choice().
Pass the dictionary keys to this function as follows:
import random
keys = list(my_dict)
country = random.choice(keys)
You may wish to track the keys that were already called in a round and when getting a fresh country, loop until the random selection is not in the list of those already "drawn"... as long as the drawn list is shorter than the keys list.
Since this is homework:
Check out random.sample() which will select and return a random element from an list. You can get a list of dictionary keys with dict.keys() and a list of dictionary values with dict.values().
I am assuming that you are making a quiz kind of application. For this kind of application I have written a function which is as follows:
def shuffle(q):
"""
The input of the function will
be the dictionary of the question
and answers. The output will
be a random question with answer
"""
selected_keys = []
i = 0
while i < len(q):
current_selection = random.choice(q.keys())
if current_selection not in selected_keys:
selected_keys.append(current_selection)
i = i+1
print(current_selection+'? '+str(q[current_selection]))
If I will give the input of questions = {'VENEZUELA':'CARACAS', 'CANADA':'TORONTO'} and call the function shuffle(questions) Then the output will be as follows:
VENEZUELA? CARACAS
CANADA? TORONTO
You can extend this further more by shuffling the options also
With modern versions of Python(since 3), the objects returned by methods dict.keys(), dict.values() and dict.items() are view objects*. And hey can be iterated, so using directly random.choice is not possible as now they are not a list or set.
One option is to use list comprehension to do the job with random.choice:
import random
colors = {
'purple': '#7A4198',
'turquoise':'#9ACBC9',
'orange': '#EF5C35',
'blue': '#19457D',
'green': '#5AF9B5',
'red': ' #E04160',
'yellow': '#F9F985'
}
color=random.choice([hex_color for color_value in colors.values()]
print(f'The new color is: {color}')
References:
*Python 3.8: Standard Library Documentation - Built-in types: Dictionary view objects
Python 3.8: Data Structures - List Comprehensions:
I just stumbled across a similar problem and designed the following solution (relevant function is pick_random_item_from_dict; other functions are just for completeness).
import random
def pick_random_key_from_dict(d: dict):
"""Grab a random key from a dictionary."""
keys = list(d.keys())
random_key = random.choice(keys)
return random_key
def pick_random_item_from_dict(d: dict):
"""Grab a random item from a dictionary."""
random_key = pick_random_key_from_dict(d)
random_item = random_key, d[random_key]
return random_item
def pick_random_value_from_dict(d: dict):
"""Grab a random value from a dictionary."""
_, random_value = pick_random_item_from_dict(d)
return random_value
# Usage
d = {...}
random_item = pick_random_item_from_dict(d)
The main difference from previous answers is in the way we handle the dictionary copy with list(d.items()). We can partially circumvent that by only making a copy of d.keys() and using the random key to pick its associated value and create our random item.
Try this (using random.choice from items)
import random
a={ "str" : "sda" , "number" : 123, 55 : "num"}
random.choice(list(a.items()))
# ('str', 'sda')
random.choice(list(a.items()))[1] # getting a value
# 'num'
To select 50 random key values from a dictionary set dict_data:
sample = random.sample(set(dict_data.keys()), 50)
I needed to iterate through ranges of keys in a dict without sorting it each time and found the Sorted Containers library. I discovered that this library enables random access to dictionary items by index which solves this problem intuitively and without iterating through the entire dict each time:
>>> import sortedcontainers
>>> import random
>>> d = sortedcontainers.SortedDict({1: 'a', 2: 'b', 3: 'c'})
>>> random.choice(d.items())
(1, 'a')
>>> random.sample(d.keys(), k=2)
[1, 3]
I found this post by looking for a rather comparable solution. For picking multiple elements out of a dict, this can be used:
idx_picks = np.random.choice(len(d), num_of_picks, replace=False) #(Don't pick the same element twice)
result = dict ()
c_keys = [d.keys()] #not so efficient - unfortunately .keys() returns a non-indexable object because dicts are unordered
for i in idx_picks:
result[c_keys[i]] = d[i]
Here is a little Python code for a dictionary class that can return random keys in O(1) time. (I included MyPy types in this code for readability):
from typing import TypeVar, Generic, Dict, List
import random
K = TypeVar('K')
V = TypeVar('V')
class IndexableDict(Generic[K, V]):
def __init__(self) -> None:
self.keys: List[K] = []
self.vals: List[V] = []
self.dict: Dict[K, int] = {}
def __getitem__(self, key: K) -> V:
return self.vals[self.dict[key]]
def __setitem__(self, key: K, val: V) -> None:
if key in self.dict:
index = self.dict[key]
self.vals[index] = val
else:
self.dict[key] = len(self.keys)
self.keys.append(key)
self.vals.append(val)
def __contains__(self, key: K) -> bool:
return key in self.dict
def __len__(self) -> int:
return len(self.keys)
def random_key(self) -> K:
return self.keys[random.randrange(len(self.keys))]
b = { 'video':0, 'music':23,"picture":12 }
random.choice(tuple(b.items())) ('music', 23)
random.choice(tuple(b.items())) ('music', 23)
random.choice(tuple(b.items())) ('picture', 12)
random.choice(tuple(b.items())) ('video', 0)

Combine 2 dictionaries by key-value in python

I have not found a solution to my question, yet I hope it's trivial.
I have two dictionaries:
dictA:
contains the order number of a word in a text as key: word as value
e.g.
{0:'Roses',1:'are',2:'red'...12:'blue'}
dictB:
contains counts of those words in the text
e.g.
{'Roses':2,'are':4,'blue':1}
I want to replace the values in dictA by values in dictB via keys in dictB, checking for nones, replacing by 0.
So output should look like:
{0:2,1:4,2:0...12:1}
Is there a way for doing it, preferentially without introducing own functions?
Use a dictionary comprehension and apply the get method of dict B to return 0 for items that are not found in B:
>>> A = {0:'Roses',1:'are',2:'red', 12:'blue'}
>>> B = {'Roses':2,'are':4,'blue':1}
>>> {k: B.get(v, 0) for k, v in A.items()}
{0: 2, 1: 4, 2: 0, 12: 1}

How to only store 3 values for a key in a dictionary? Python

So I tried to only allow the program to store only last 3 scores(values) for each key(name) however I experienced a problem of the program only storing the 3 scores and then not updating the last 3 or the program appending more values then it should do.
The code I have so far:
#appends values if a key already exists
while tries < 3:
d.setdefault(name, []).append(scores)
tries = tries + 1
Though I could not fully understand your question, the concept that I derive from it is that, you want to store only the last three scores in the list. That is a simple task.
d.setdefault(name,[]).append(scores)
if len(d[name])>3:
del d[name][0]
This code will check if the length of the list exceeds 3 for every addition. If it exceeds, then the first element (Which is added before the last three elements) is deleted
Use a collections.defaultdict + collections.deque with a max length set to 3:
from collections import deque,defaultdict
d = defaultdict(lambda: deque(maxlen=3))
Then d[name].append(score), if the key does not exist the key/value will be created, if it does exist we will just append.
deleting an element from the start of a list is an inefficient solution.
Demo:
from random import randint
for _ in range(10):
for name in range(4):
d[name].append(randint(1,10))
print(d)
defaultdict(<function <lambda> at 0x7f06432906a8>, {0: deque([9, 1, 1], maxlen=3), 1: deque([5, 5, 8], maxlen=3), 2: deque([5, 1, 3], maxlen=3), 3: deque([10, 6, 10], maxlen=3)})
One good way for keeping the last N items in python is using deque with maxlen N, so in this case you can use defaultdict and deque functions from collections module.
example :
>>> from collections import defaultdict ,deque
>>> l=[1,2,3,4,5]
>>> d=defaultdict()
>>> d['q']=deque(maxlen=3)
>>> for i in l:
... d['q'].append(i)
...
>>> d
defaultdict(<type 'collections.deque'>, {'q': deque([3, 4, 5], maxlen=3)})
A slight variation on another answer in case you want to extend the list in the entry name
d.setdefault(name,[]).extend(scores)
if len(d[name])>3:
del d[name][:-3]
from collections import defaultdict
d = defaultdict(lambda:[])
d[key].append(val)
d[key] = d[key][:3]
len(d[key])>2 or d[key].append(value) # one string solution

Categories

Resources