Python nested dictionary iterate and create new dictionary - python

I have a python dictionary where I am iterating it and creating a new dictionary with some calculations. My current code is working fine but I want to know what are the other different ways to do it?
The logic is-
1. if the same `room` has multiple `rate` codes then append it to that room.
2. get the lowest price for every room type.
Existing Code:
datas = [
{"code": "A1KXDY", "room": "A1K", "rate": "XDX", "price": 10},
{"code": "B1KXDY", "room": "B1K", "rate": "XDX", "price": 20},
{"code": "C1KXDY", "room": "C1K", "rate": "XDX", "price": 30},
{"code": "A1KXDY", "room": "A1K", "rate": "XDY", "price": 5},
{"code": "B1KXDY", "room": "B1K", "rate": "XDY", "price": 10},
{"code": "C1KXDY", "room": "C1K", "rate": "XDY", "price": 40},
]
final_result = {}
for data in datas:
display_rate = data["price"]
if data["room"] in final_result:
existing_room = final_result[data["room"]]
existing_room["rate_codes"].append(data["rate"])
current_rate = existing_room["display_rate"]
if current_rate > display_rate:
existing_room["display_rate"] = display_rate
continue
room_data = {"display_rate": display_rate, "rate_codes": [data["rate"]]}
final_result[data["room"]] = room_data
print(final_result)
Expected Output:
{'A1K': {'display_rate': 5, 'rate_codes': ['XDX', 'XDY']}, 'B1K': {'display_rate': 10, 'rate_codes': ['XDX', 'XDY']}, 'C1K': {'display_rate': 30, 'rate_codes': ['XDX', 'XDY']}}

You can use pandas for this.
import pandas as pd
datas = [...]
df = pd.DataFrame(datas)
df = df.groupby('room', as_index=False).agg({'price': min, 'rate': list})
df.rename(columns={'price': 'display_rate', 'rate': 'rate_codes'}, inplace=True)
result = df.to_dict('records')
Output:
[{'room': 'A1K', 'display_rate': 5, 'rate_codes': ['XDX', 'XDY']},
{'room': 'B1K', 'display_rate': 10, 'rate_codes': ['XDX', 'XDY']},
{'room': 'C1K', 'display_rate': 30, 'rate_codes': ['XDX', 'XDY']}]
The output can further be treated to match the output you want for final_result.

I would say your solution is fine. What constitutes "more smartly" is subjective to a large extent anyway.
I suppose, if you were willing to accept a set of rate_codes in the final result instead of a list, you could get away with fairly few lines of code:
final_result = {}
for data in datas:
room_key = data["room"] # just for readability
final_result.setdefault(room_key, {
"display_rate": data["price"],
"rate_codes": {data["rate"]}
})
final_result[room_key]["display_rate"] = min(
final_result[room_key]["display_rate"],
data["price"]
)
final_result[room_key]["rate_codes"].add(data["rate"])
Using the dict.setdefault method does nothing to final_result if it already has the key room_key; otherwise it inserts it with the value of that new dictionary.
We can use the min function instead of explicitly comparing values.
And we use the fact that a set always has unique values, so we can just call its add method without needing to check if that rate code already exists.
The result:
{'A1K': {'display_rate': 5, 'rate_codes': {'XDX', 'XDY'}}, 'B1K': {'display_rate': 10, 'rate_codes': {'XDX', 'XDY'}}, 'C1K': {'display_rate': 30, 'rate_codes': {'XDX', 'XDY'}}}

Related

How to change all nested dictionary values

Using the code here, how would I change the value of stock for all the products with newstock?
products = {
"apple": {"price": 3.5, "stock": 134},
"banana": {"price": 6.82, "stock": 52},
"cake": {"price": 23, "stock": 5}
}
newstock = input("Enter amount to set :")
I assume a for loop like this would work
for x in products:
products []["stock"]=newstock
But I'm not sure what to put in the empty []
Here is what you want to do
products = {
"apple": {"price": 3.5, "stock": 134},
"banana": {"price": 6.82, "stock": 52},
"cake": {"price": 23, "stock": 5}
}
newstock = int(input("Enter amount to set :"))
for i in products.values():
i['stock'] = newstock
values() is a method that access your dict values that being the other dicts.
You can access a dict value by naming it is key inside brackets. You can loop through the multiple values and there you go.
You're close. Change
products []["stock"]=newstock
to
products[x]["stock"]=newstock
x is the dictionary key, you need to index the dictionary with it.

Python: merging 2 dictionaries (from csv file) with same key AND values

I have these two DictReader-ed csv files:
A = {"Name": "Alex", "Age": 17} {"Name": "Bob", "Age": 20"} {"Name": "Clark", "Age": 24"}
B = {"Age": 17, "Class": "Economy"} {"Age": 24, "Class": "IT"} {"Age":17, "Class": Arts}
and several more bracketed values.
Is it possible to join them to form this:
{"Name": "Alex", "Age": 17, "Class": [{"Economy"}, {"Arts"}]}
{"Name": "Clark", "Age": 24, "Class": [{"IT"}]}
In short, joining them when they have the same Age and put all the same classes into a list?
So far I've only read both dicts:
import csv
A=open('A.csv')
A_reader = csv.DictReader(A)
B=open('B.csv')
B_reader = csv.DictReader(B)
for item in A_reader:
print(item)
for item in B_reader:
print(item)
but unsure of how to merge them as mentioned.
Thank you!
EDIT: The csv given is so that no two people will have the same age.
import copy
A = [{"Name": "Alex", "Age": 17}, {"Name": "Bob", "Age": 20}, {"Name": "Clark", "Age": 24}]
B = [{"Age": 17, "Class": "Economy"}, {"Age": 24, "Class": "IT"}, {"Age":17, "Class": "Arts"}]
C = []
for a in A:
c = copy.copy(a)
c["Class"] = []
for b in B:
if a["Age"]==b["Age"]:
c["Class"].append(b["Class"])
C.append(c)
Result is:
[{'Name': 'Alex', 'Age': 17, 'Class': ['Economy', 'Arts']},
{'Name': 'Bob', 'Age': 20, 'Class': []},
{'Name': 'Clark', 'Age': 24, 'Class': ['IT']}]
If it doesn't work for you, let me know :)
I'd first turn B into a dictionary {age: [classes]}, then loop over A and combine the dictionaries – the more data you have, the more efficient it will be compared to looping over B over and over again. I'd use a collections.defaultdict for that.1
from collections import defaultdict
# {17: ['Economy', 'Arts'], 24: ['IT']}
classes_by_age = defaultdict(list)
for b in B:
classes_by_age[b['Age']].append(b['Class'])
With that in place, all you need to do is merge the dictionaries. I guess one of the most concise ways to do that is by passing a combination of the double asterisk operator ** and a keyword argument to the dict constructor:
merged = [dict(**a, Classes=classes_by_age[a['Age']]) for a in A]
1 If you don't want to import defaultdict, you can simply initialize classes_by_age as an empty dictionary and in the loop do:
for b in B:
age = b['Age']
class_ = b['Class']
if age in classes_by_age:
classes_by_age[age].append(class_)
else:
classes_by_age[age] = [class_]
But then you'd also have do adopt the final list comprehension to the one below, otherwise empty Classes would cause trouble:
[dict(**a, Classes=classes_by_age.get(a['Age'], [])) for a in A]
You mentioned Pandas in a comment. If that is an option, then you could try:
import pandas as pd
df_A = pd.read_csv("A.csv")
df_B = pd.read_csv("B.csv")
result = df_A.merge(
df_B.groupby("Age")["Class"].agg(list), on="Age", how="left"
).to_dict("records")

Updating value in json object in python

I'm struggling with updating a value in a json object.
import json
userBoard = '' #see example below. is loaded in a separate function
#app.get("/setItem")
def setItem():
id = request.args.get('itemId')
id = int(id[2:]) # is for instance 2
for item in json.loads(session['userBoard']):
if item['id'] == id:
item['solved']='true'
else:
print('Nothing found!')
return('OK')
Example of the json:
[{"id": 1, "name": "t1", "solved": "false"}, {"id": 2, "name": "t2", "solved": "false"}, {"id": 3, "name": "t3"}]
However, when I check the printout of the userBoard, the value is still 'false'. Does anyone have any idea? Does this need to be serialized somehow? Tried many things but it didn't work out...
Many thanks!
One could say the question is somehow specific and is lacking some information to provide a simple answer. So I am going to make some assumptions and propose a solution.
First, id and input are python built-ins and should not be used as variable names. I will use these strings with a _ prefix on purpose, so that you could still use these names in a safer way.
import json
from typing import List
json_ex = '[{"id": 1, "name": "t1", "solved": "false"}, {"id": 2, "name": "t2", "solved": "false"}, {"id": 3, "name": "t3"}]'
_id = 2 # for now a constant for demonstration purposes
def setItem(_input: List[dict]):
for item in _input:
if (this_id := item['id']) == _id: # requires python 3.8+, otherwise you can simplify this
item['solved'] = 'true'
print(f'Updated item id {this_id}')
else:
print('Nothing found!')
json_ex_parsed = json.loads(json_ex) # this is now a list of dictionaries
setItem(json_ex_parsed)
Output:
Nothing found!
Updated item id 2
Nothing found!
The contents of json_ex_parsed before applying setItem:
[{'id': 1, 'name': 't1', 'solved': 'false'},
{'id': 2, 'name': 't2', 'solved': 'false'},
{'id': 3, 'name': 't3'}]
and after:
[{'id': 1, 'name': 't1', 'solved': 'false'},
{'id': 2, 'name': 't2', 'solved': 'true'}, # note here has been updated
{'id': 3, 'name': 't3'}]

working with list of dictionaries in python

I have a list of dictionaries look bellow
raw_list = [
{"item_name": "orange", "id": 12, "total": 2},
{"item_name": "apple", "id": 12},
{"item_name": "apple", "id": 34, "total": 22},
]
Expected output should be
[
{"item_name": ["orange", "apple"], "id": 12, "total": 2},
{"item_name": "apple", "id": 34, "total": 22},
]
but how i got
[
{"item_name": "orangeapple", "id": 12, "total": 2},
{"item_name": "apple", "id": 34, "total": 22},
]
Here my code bellow
comp_key = "id"
conc_key = "item_name"
res = []
for ele in test_list:
temp = False
for ele1 in res:
if ele1[comp_key] == ele[comp_key]:
ele1[conc_key] = ele1[conc_key] + ele[conc_key]
temp = True
break
if not temp:
res.append(ele)
how to resolve...?
Something like this - the special sauce is the isinstance stuff to make sure you're making the concatenated value a list instead.
Do note that this assumes the raw list is ordered by the comp_key (id), and will misbehave if that's not the case.
raw_list = [
{"item_name": "orange", "id": 12, "total": 2},
{"item_name": "apple", "id": 12},
{"item_name": "apple", "id": 34, "total": 22},
]
comp_key = "id"
conc_key = "item_name"
grouped_items = []
for item in raw_list:
last_group = grouped_items[-1] if grouped_items else None
if not last_group or last_group[comp_key] != item[comp_key]: # Starting a new group?
grouped_items.append(item.copy()) # Shallow-copy the item into the result array
else:
if not isinstance(last_group[conc_key], list):
# The concatenation key is not a list yet, make it so
last_group[conc_key] = [last_group[conc_key]]
last_group[conc_key].append(item[conc_key])
print(grouped_items)
import pandas as pd
df = pd.DataFrame(raw_list)
dd = pd.concat([df.groupby('id')['item_name'].apply(list), df.groupby('id').['total'].apply(sum)], axis=1).reset_index()
dd.to_dict('records')
you could use pandas to group by and apply function to two columns then convert to dict
[{'id': 12, 'item_name': ['orange', 'apple'], 'total': 2.0},
{'id': 34, 'item_name': ['apple'], 'total': 22.0}]
You could use itertools.grouper for grouping by id and collections.defaultdict to combine values with same keys into lists.
from itertools import groupby
from collections import defaultdict
id_getter = lambda x: x['id']
gp = groupby(sorted(raw_list, key=id_getter), key=id_getter)
out = []
for _,i in gp:
subdict = defaultdict(list)
for j in i:
for k,v in j.items():
subdict[k].append(v)
out.append(dict(subdict))
out
Working with complex datatypes such as nested lists and dictionaries, I would advice really utilizing the APIs provided by collections and itertools.

Determining Turn Order

I wanted to try it like this, but I don't want to nest a bunch of for loops or if statements. For some reason I'm not seeing what the actual comparison should be to take the speeds list and populate turn order. Basically just comparing what each individual minion's speed is against the others using the sort function in it's own method and returning a list. Then I want to compare the list values against the key values stored in the minion_collection. I'm not sure if I'm setting myself up for a world of hurt here or not. Still somewhat new to python so I don't know many of the utilities.
minion_collection = {
'minion1' : {
"name" : "zombie", "str" : 10, "def" : 5, "hp" : 25, "maxhp" : 25, "speed" : 15, "spdbar": 0},
'minion2' : {
"name": 'cleric', "str": 4, "def": 5, "hp": 25,"maxhp": 25, "speed": 20, "spdbar": 0},
'minion3' : {
"name" : "professor", "str" : 10, "def" : 5, "hp" : 25, "maxhp" : 25, "speed" : 15, "spdbar": 0},
'minion4' : {
"name": 'hunter', "str": 4, "def": 5, "hp": 25,"maxhp": 25, "speed": 30, "spdbar": 0}
}
def initiative(speeds):
for x in minion_collection:
minion_collection[x]['spdbar'] = 100
order = sort(speeds, reverse = True)
return order
Where I start to determine the turn order
turn = 1
speeds = (fighter1['speed'], fighter2['speed'], pcfighter1['speed'], pcfighter2['speed'])
order = initiative(speeds)
for speed in order:
if fighter1['speed'] == speed:
first = fighter1
second = '???'
third = '???'
last = '???'
In this case you can use sorted()
turn = 1
speeds = (fighter1['speed'], fighter2['speed'], pcfighter1['speed'], pcfighter2['speed'])
order = initiative(speeds)
for speed in sorted(order):
if fighter1['speed'] == speed:
first = fighter1
second = '???'
third = '???'
last = '???'

Categories

Resources