Updating key values in dictionaries - python

I am trying to write code for the following:
The idea is to have a storage/inventory dictionary and then have the key values be reduced by certain household tasks. E.g. cleaning, cooking etc.
This would be the storage dictionary:
cupboard= {"cookies":30,
"coffee":3,
"washingpowder": 5,
"cleaningspray": 5,
'Pasta': 0.5,
'Tomato': 4,
'Beef': 2,
'Potato': 2,
'Flour': 0.2,
'Milk': 1,
"Burger buns": 6}
now this is the code that I wrote to try and reduce one single key's value (idea is that the action "cleaning" reduces the key "cleaning spray" by one cleaning unit = 0.5
cleaning_amount = 0.5
def cleaning(room):
while cupboard["cleaningspray"] <0.5:
cleaned = {key: cupboard.get(key) - cleaning_amount for key in cupboard}
return cupboard
livingroom = 1*cleaning_amount
cleaning(livingroom)
print(cupboard)
but it returns this, which is the same dictionary as before, with no updated values
{'cookies': 30, 'coffee': 3, 'washingpowder': 5, 'cleaningspray': 5, 'Pasta': 0.5, 'Tomato': 4, 'Beef': 2, 'Potato': 2, 'Flour': 0.2, 'Milk': 1, 'Burger buns': 6}
Can anybody help?
Thank you!!
picture attached to see indents etc.

I guess you want to decrease the "cleaningspray" amount depending on the room size (or other factors). I would do it like this:
cleaning_amount = 0.5
def cleaning(cleaning_factor):
if cupboard["cleaningspray"] > 0.5:
# reduce the amount of cleaning spray depending on the cleaning_factor and the global cleaning_amount
cupboard["cleaningspray"] -= cleaning_factor * cleaning_amount
livingroom_cleaning_factor = 1
cleaning(livingroom_cleaning_factor)
print(cupboard)
Output:
{'cookies': 30, 'coffee': 3, 'washingpowder': 5, 'cleaningspray': 4.5, 'Pasta': 0.5, 'Tomato': 4, 'Beef': 2, 'Potato': 2, 'Flour': 0.2, 'Milk': 1, 'Burger buns': 6}

So I believe the reason that the values don't change is because it is being done in a for loop.
e.g.
list_values = [1, 2, 3, 4, 5]
new_variable = [num + 1 for num in list_values]
print("list_values", list_values) # The original list_values variable doesn't change
print("new_variable", new_variable) # This new variable holds the required value
This returns:
list_values [1, 2, 3, 4, 5]
new_variable [2, 3, 4, 5, 6]
So to fix the problem, you can use the 'new_variable'
So, now that the concept is clear (I hope), in your case it would be something like this
def cleaning():
while cupboard["cleaningspray"] > 0.5: # Also here, i beleive you intend to have `>`
#and not `<` in the original code
cleaned = {key: cupboard.get(key) - cleaning_amount for key in cupboard}
return cleaned
We return the 'new_variable' that is cleaned
so it can be assigned to the original dictionary variable as follows if required:
cupboard = cleaning()
EDIT:
Also, as #d-k-bo commented, if you intend to only have the operation carry out once... an if statement would also do the job
if cupboard["cleaningspray"] > 0.5: # Again assuming you intended '>' and not '<'
Otherwise, you should keep the return statement outside the while loop

Related

Convert a frozenset to a dictionary in python

I have the following frozenset:
f_set = [frozenset({8, 14, 15, 18}), frozenset({1, 2, 3, 7, 8}), frozenset({0, 4, 5})]
I need to convert f_set into a dictionary as the following
For the first set, I need the dictionary to have a value of 0.
For the second set, I need the dictionary to have a value of 1.
For the third set, I need the dictionary to have a value of 2.
Now, in case some keys are existed in multiple set, assign a new values to them. In this case 8 existed in both set 1 and set 2, so assign a value of 3.
dict1 = {8:3, 14:0, 15:0, 18:0, 1:1, 2:1, 3:1, 7:1, 0:2, 4:2, 5:2}
Note: my actual f_set contains more than three sets, so I'd like to avoid doing that manually.
You can use dict comprehension with enumerate:
f_set = [frozenset({8, 14, 15, 18}), frozenset({1, 2, 3, 7, 8}), frozenset({0, 4, 5})]
dict1 = {x: i for i, s in enumerate(f_set) for x in s}
print(dict1)
# {8: 1, 18: 0, 14: 0, 15: 0, 1: 1, 2: 1, 3: 1, 7: 1, 0: 2, 4: 2, 5: 2}
Note that, if the sets are not mutually disjoint, some keys will be discarded, since a dict cannot have duplicate keys.
You can simply loop over the frozensets to set each of them in an output dictionary:
output = dict()
for i in range(len(f_set)):
for s in f_set[i]:
output[s] = i
Note although the order may be different from what you have, order shouldn't matter in the dictionary.

How to avoid code duplication in unit tests

Suppose I have a function called "factorial" and I want to test this function. I often find myself rewriting unit tests like the one shown below where I define some test cases, possibly including some edge cases, running tests for all of those. This common pattern, defining the test values and expected output and running tests on them leaves me with the following boilerplate code. Essentially I would like to have one function to which I pass the list of test values with the list of expected values and the function to test it on and let the framework handle the rest for me. Does something like that exist and what would speaks against such a simplified approach?
import unittest
class TestRecursionAlgorithms(unittest.TestCase):
def test_factorial(self):
input_values = [1, 2, 3, 4, 5]
solutions = [1, 2, 6, 24, 120]
for idx, (input_value, expected_solution) in enumerate(zip(input_values, solutions)):
with self.subTest(test_case=idx):
self.assertEqual(expected_solution, factorial(input_value))
Cheers
You could use a variation of this.
input_values = [1, 2, 3, 4, 5]
solutions = [1, 2, 6, 24, 120]
result = dict(zip(input_values, solutions)) # Key:Value
print(result)
match = {i: k for i, k in result.items() if i == k} # Key Value comparison
print(match)
result:
{1: 1, 2: 2, 3: 6, 4: 24, 5: 120}
{1: 1, 2: 2}

connecting two dictionaries and storing it into an RDD

I have a dictionary users with 1748 elements as (showing only the first 12 elements)-
defaultdict(int,
{'470520068': 1,
'2176120173': 1,
'145087572': 3,
'23047147': 1,
'526506000': 1,
'326311693': 1,
'851106379': 4,
'161900469': 1,
'3222966471': 1,
'2562842034': 1,
'18658617': 1,
'73654065': 4,})
and another dictionary partition with 452743 elements as(showing first 42 elements)-
{'609232972': 4,
'975151075': 4,
'14247572': 4,
'2987788788': 4,
'3064695250': 2,
'54097674': 3,
'510333371': 0,
'34150587': 4,
'26170001': 0,
'1339755391': 3,
'419536996': 4,
'2558131184': 2,
'23068646': 6,
'2781517567': 3,
'701206260771905541': 4,
'754263126': 4,
'33799684': 0,
'1625984816': 4,
'4893416104': 3,
'263520530': 3,
'60625681': 4,
'470528618': 3,
'4512063372': 6,
'933683112': 3,
'402379005': 4,
'1015823005': 2,
'244673821': 0,
'3279677882': 4,
'16206240': 4,
'3243924564': 6,
'2438275574': 6,
'205941266': 3,
'330723222': 1,
'3037002897': 0,
'75454729': 0,
'3033154947': 6,
'67475302': 3,
'922914019': 6,
'2598199242': 6,
'2382444216': 3,
'1388012203': 4,
'3950452641': 5,}
The keys in users(all unique) are all in partition and also are repeated with different values(and also partition contains some extra keys which is not of our use). What I want is a new dictionary final which connects the keys of users matching with those of partition with the values of partition, i.e. if I have '145087572' as a key in users and the same key has been repeated twice or thrice in partition with different values as: {'145087572':2, '145087572':3,'145087572':7} then I should get all these three elements in the new dictionary final. Also I have to store this dictionary as a key:value RDD.
Here's what I tried:
user_key=list(users.keys())
final=[]
for x in user_key:
s={x:partition.get(x) for x in partition}
final.append(s)
After running this code my laptop stops to respond (the code still shows [*]) and I have to restart it. May I know that is there any problem with my code and a more efficient way to do this.
First dictionary cannot hold duplicate keys, duplicate key's value will be ovewritten by the last value of same key.
Now lets analyze your code
user_key=list(users.keys()) # here you get all the keys say(1,2,3)
final=[]
for x in user_key: #you are iterating over the keys so x will be 1, 2, 3
s={x:partition.get(x) for x in partition} #This is the reason for halting
''' breaking the above line this is what it looks like.
s = {}
for x in partition:
s[x] = partition.get(x)
isn't the outer forloop and inner forloop is using the same variable x
so basically instead of iterating over the keys of users you are
iterating over the keys of partition table,
as x is updated inside inner foorloop(so x contains the keys of partition
table).
'''
final.append(s)
Now the reason for halting is (say you have 10 keys in users dictionary).
so outer forloop will iterate 10 times and for the 10 times
Inner forloop will iterate over whole partition keys and make a copy
which is causing memory error and eventually your system gets hung due to out of memory.
I think this will work for you
store partition data in a python defaultdict(list)
from collections import defaultdict
user_key = users.keys()
part_dict = defaultdict(list)
# partition = [[key1, value], [key2, value], ....]
# store your parition data in this way (list inside list)
for index in parition:
if index[0] not in part_dict:
part_dict[index[0]] = index[1]
else:
part_dict[index[0]].append(index[1])
# part_dict = {key1:[1,2,3], key2:[1,2,3], key3:[4,5],....}
final = []
for x in user_keys:
for values in part_dict[x]:
final.append([x, values])
# if you want your result of dictionary format(I don't think it's required) then you ca use
# final.append({x:values})
# final = [{key1: 1}, {key2: 2}, ....]
# final = [[key1, 1], [key1, 2], [key1, 3], .....]
The above code is not tested, some minor changes may be required

Inserting a set of elements alternatively

There is a list with elements of similar nature (4 7's,3 5's, etc.) that I want to insert in right left order into a another list ().
newlst = []
lst = [7, 7, 7, 7, 5, 5, 5, 3, 3, 3, 2, 2]
So the first thing being inserted into newlst is the group of 7's:
newlst = [7,7,7,7]
Subsequently, the group of 5's is inserted into the list on the right:
newlst = [7, 7, 7, 7, 5, 5, 5]
And then the group of 3's is inserted on the left, and after that the group of 2's is inserted on the right. The final list looks like this
newlst = [3, 3, 3, 7, 7, 7, 7, 5, 5, 5, 2, 2]
In order to add elements in the list on a right left basis, I did this:
for i in lst:
lst.insert(0,i)
else:
lst.append(i)
The insert method inserts elements into the 0 index (which is the right of the list) and append adds elements at the end of the list (which is the left of the list). However, I'm having problems adding the group of elements into the newlst. To that end, I thought using a dictionary would be a good idea.
myDict = {2: 2, 3: 3, 5: 3, 7: 4}
EDIT:
for k, v in myDict.items():
if k in lst:
for i in range(v):
lst.append(i)
else:
lst.insert(0,i)
The intention of this dictionary is for each key, I want to insert the key value 'x' times, e.g. the key 7, would be inserted 4 times: [7,7,7,7]. Is there a way to achieve this in Python so I can get the output newlist: [3, 3, 3, 7, 7, 7, 7, 5, 5, 5, 2, 2] ?
You can accomplish this pretty easily with a deque, along with cycle and groupby
from collections import deque
from itertools import groupby, cycle
#creates a deque object
d = deque()
#creates a repeating iterator to alternate function calls
c = cycle([d.extendleft, d.extend])
lst = [7, 7, 7, 7, 5, 5, 5, 3, 3, 3, 2, 2]
for _, items in groupby(lst):
#calls the alternated function to extend the items
next(c)(items)
print(list(d))
>>> [3, 3, 3, 7, 7, 7, 7, 5, 5, 5, 2, 2]
Here is your initial code:
newlst = []
lst = [7, 7, 7, 7, 5, 5, 5, 3, 3, 3, 2, 2]
myDict = {2: 2, 3: 3, 5: 3, 7: 4}
for k, v in myDict.items():
if k in lst:
for i in range(v):
lst.append(i)
else:
lst.insert(0,i)
You have a few major problems here:
k is always in lst, by definition. That means your check is not a valid way to alternate.
Your data is getting appended/prepended to lst instead of newlst.
A dict is a hash-table. This means that the order of the keys will pretty much never be in the order you defined them in.
The first item can be solved through enumeration:
for i, (k, v) in enumerate(myDict.items()):
if i % 2:
newlst = [k] * v + newlst
else:
newlst += [k] * v
I've fixed the list you are appending to, and am using [k] * v to construct the prepended/appended list. newlst += [k] * v is equivalent to newlst.extend([k] * v). However, keep in mind that newlst = [k] * v + newlst creates a new list object rather than concatenating in-place.
The third item can be fixed using OrderedDict instead of a regular dict:
from collections import OrderedDict
...
myDict = OrderedDict([(2, 2), (3, 3), (5, 3), (7, 4)])
That will make the keys run in the order that you want. In fact, you don't need to construct myDict by hand at all. You can combine OrderedDict with a Counter to get the exact same result dynamically. The recipe for this is given in the OrderedDict docs:
from collections import Counter, OrderedDict
...
class OrderedCounter(Counter, OrderedDict):
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))
def __reduce__(self):
return self.__class__, (OrderedDict(self),)
myDict = OrderedCounter(lst)
All this is pretty verbose and not very efficient. As #Wondercricket's answer points out, you can use the functions in itertools to perform the same task using generators.
This is what you want to do?
list = [7, 7, 7, 7, 5, 5, 5, 3, 3, 3, 2, 2]
def list_to_answerlist(list, default=[]):
if not list:
return default
else:
result = []
direction = True # Insert Direction: True = Insert Left / False = Insert Right
actual_group = list[0]
for element in list:
if (element != actual_group):
direction = not direction # Change Insert Direction
actual_group = element # Update Actual Group
if direction: # Insert Left
result.insert(0,element)
else: # Insert Right
result.append(element)
return result
new_list = list_to_answerlist(list) # output = [3, 3, 3, 7, 7, 7, 7, 5, 5, 5, 2, 2]

Storing randomly generated dictionaries in Python

I have a series of functions that end up giving a list, with the first item containing a number, derived from the dictionaries, and the second and third items are dictionaries.
These dictionaries have been previously randomly generated.
The function I am using generates a given number of these dictionaries, trying to get the highest number possible as the first item. (It's designed to optimise dice rolls).
This all works fine, and I can print the value of the highest first item from all iterations. However, when I try and print the two dictionaries associated with this first number (bearing in mind they're all in a list together), it just seemingly randomly generates the two other dictionaries.
def repeat(type, times):
best = 0
for i in range(0, times):
x = rollForCharacter(type)
if x[0] > best:
print("BEST:", x)
best = x[0]
print("The highest average success is", best)
return best
This works great. The last thing shown is:
BEST: (3.58, [{'strength': 4, 'intelligence': 1, 'charisma': 1, 'stamina': 4, 'willpower': 2, 'dexterity': 2, 'wits': 5, 'luck': 2}, {'agility': 1, 'brawl': 2, 'investigation': 3, 'larceny': 0, 'melee': 1, 'survival': 0, 'alchemy': 3, 'archery': 0, 'crafting': 0, 'drive': 1, 'magic': 0, 'medicine': 0, 'commercial': 0, 'esteem': 5, 'instruction': 2, 'intimidation': 2, 'persuasion': 0, 'seduction': 0}])
The highest average success is 3.58
But if I try something to store the list which gave this number:
def repeat(type, times):
best = 0
bestChar = []
for i in range(0, times):
x = rollForCharacter(type)
if x[0] > best:
print("BEST:", x)
best = x[0]
bestChar = x
print("The highest average success is", best)
print("Therefore the best character is", bestChar)
return best, bestChar
I get this as the last result, which is fine:
BEST: (4.15, [{'strength': 2, 'intelligence': 3, 'charisma': 4, 'stamina': 4, 'willpower': 1, 'dexterity': 2, 'wits': 4, 'luck': 1}, {'agility': 1, 'brawl': 0, 'investigation': 5, 'larceny': 0, 'melee': 0, 'survival': 0, 'alchemy': 7, 'archery': 0, 'crafting': 0, 'drive': 0, 'magic': 0, 'medicine': 0, 'commercial': 1, 'esteem': 0, 'instruction': 3, 'intimidation': 0, 'persuasion': 0, 'seduction': 0}])
The highest average success is 4.15
but the last line is
Therefore the best character is (4.15, [{'strength': 1, 'intelligence': 3, 'charisma': 4, 'stamina': 4, 'willpower': 1, 'dexterity': 2, 'wits': 2, 'luck': 3}, {'agility': 1, 'brawl': 0, 'investigation': 1, 'larceny': 4, 'melee': 2, 'survival': 0, 'alchemy': 2, 'archery': 4, 'crafting': 0, 'drive': 0, 'magic': 0, 'medicine': 0, 'commercial': 1, 'esteem': 0, 'instruction': 0, 'intimidation': 2, 'persuasion': 1, 'seduction': 0}])
As you can see this doesn't match with what I want, and what is printed literally right above it.
Through a little bit of checking, I realised what it gives out as the "Best Character" is just the last one generated, which is not the best, just the most recent. However, it isn't that simple, because the first element IS the highest result that was recorded, just not from the character in the rest of the list. This is really confusing because it means the list is somehow being edited but at no point can I see where that would happen.
Am I doing something stupid whereby the character is randomly generated every time? I wouldn't think so since x[0] gives the correct result and is stored fine, so what changes when it's the whole list?
From the function rollForCharacter() it returns rollResult, character which is just the number and then the two dictionaries.
I would greatly appreciate it if anyone could figure out and explain where I'm going wrong and why it can print the correct answer to the console yet not store it correctly a line below!
EDIT:
Dictionary 1 Code:
attributes = {}
def assignRow(row, p): # p is the number of points you have to assign to each row
rowValues = {}
for i in range(0, len(row)-1):
val = randint(0, p)
rowValues[row[i]] = val + 1
p -= val
rowValues[row[-1]] = p + 1
return attributes.update(rowValues)
def getPoints():
points = [7, 5, 3]
shuffle(points)
row1 = ['strength', 'intelligence', 'charisma']
row2 = ['stamina', 'willpower']
row3 = ['dexterity', 'wits', 'luck']
for i in range(0, len(points)):
row = eval("row" + str(i+1))
assignRow(row, points[i])
Dictionary 2 Code:
skills = {}
def assignRow(row, p): # p is the number of points you have to assign to each row
rowValues = {}
for i in range(0, len(row) - 1):
val = randint(0, p)
rowValues[row[i]] = val
p -= val
rowValues[row[-1]] = p
return skills.update(rowValues)
def getPoints():
points = [11, 7, 4]
shuffle(points)
row1 = ['agility', 'brawl', 'investigation', 'larceny', 'melee', 'survival']
row2 = ['alchemy', 'archery', 'crafting', 'drive', 'magic', 'medicine']
row3 = ['commercial', 'esteem', 'instruction', 'intimidation', 'persuasion', 'seduction']
for i in range(0, len(points)):
row = eval("row" + str(i + 1))
assignRow(row, points[i])
It does look like the dictionary is being re-generated, which could easily happen if the function rollForCharacter returns either a generator or alternatively is overwriting a global variable which is being overwritten by a subsequent cycle of the loop.
A simple-but-hacky way to solve the problem would be to take a deep copy of the dictionary at the time of storing, so that you're sure you're keeping the values at that point:
def repeat(type, times):
best = 0
bestChar = []
for i in range(0, times):
x = rollForCharacter(type)
if x[0] > best:
print("BEST:", x)
best = x[0]
# Create a brand new tuple, containing a copy of the current dict
bestChar = (x[0], x[1].copy())
The correct answer would be however to pass a unique dictionary variable that is not affected by later code.
See this SO answer with a bit more context about how passing a reference to a dictionary can be risky as it's still a mutable object.

Categories

Resources