Nested dictionary representing catalogue - python

A text file has information about after class activities (name, price per month, days and time) that looks like so:
Swimming,20,Monday,15,Monday,17,Wednesday,18,Friday,15
Football,20,Tuesday,18,Wednesday,17,Wednesday,18,Thursday,19
Ballet,40,Monday,18,Tuesday,18,Wednesday,16,Thursday,16,Friday,17
To represent the course catalogue, I've created a nested dictionary in a format like this:
{'Swimming': {'Price': '20', 'Dates': {'Monday': ['15', '17'], 'Wednesday': ['18'], 'Friday': ['15']}}, 'Football': {'Price': '20', 'Dates': {'Tuesday': ['18'], 'Wednesday': ['17', '18'], 'Thursday': ['19']}}, 'Ballet': {'Price': '40', 'Dates': {'Monday': ['18'], 'Tuesday': ['18'], 'Wednesday': ['16'], 'Thursday': ['16'], 'Friday': ['17']}}}
And the code looks like this:
with open("fil.txt", "r") as f:
catalogue = {}
while True:
content = f.readline().strip()
if not content: break
content = content.split(',')
u[content[0]] = {}
u[content[0]]['Price'] = content[1]
u[content[0]]['Dates'] = {}
for i in range(2,len(content),2):
if content[i] in u[content[0]]['Dates']:
u[content[0]]['Dates'][content[i]].append(content[i+1])
else:
u[content[0]]['Dates'][content[i]] = [content[i+1]]
My question is : is there a simpler way to implement such dictionary? Or maybe another data structure should have been used to represent catalogue rather than this one?

This is one way using a nested dictionary structure via collections.defaultdict.
from collections import defaultdict
u = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
with open("fil.txt", "r") as f:
catalogue = {}
while True:
content = f.readline().strip()
if not content: break
content = content.split(',')
u[content[0]]['Price'] = content[1]
for i in range(2,len(content),2):
u[content[0]]['Dates'][content[i]].append(content[i+1])

I would simply write a class.
from collections import defaultdict
class SportClass:
def __init__(self, name, price, *times):
self.name = name
self.price = float(price)
self.days = defaultdict(list)
for day, hour in zip(times[::2], times[1::2]):
self.days[day].append(int(hour))
with open('fil.txt') as fp:
classes = [SportClass(*line.split(',')) for line in fp if line.strip()]

Related

How to avoid very long if-elif-elif-else statements in Python function

Is there a smart way to shorten very long if-elif-elif-elif... statements?
Let's say I have a function like this:
def very_long_func():
something = 'Audi'
car = ['VW', 'Audi', 'BMW']
drinks = ['Cola', 'Fanta', 'Pepsi']
countries = ['France', 'Germany', 'Italy']
if something in car:
return {'type':'car brand'}
elif something in drinks:
return {'type':'lemonade brand'}
elif something in countries:
return {'type':'country'}
else:
return {'type':'nothing found'}
very_long_func()
>>>> {'type': 'car brand'}
The actual function is much longer than the example. What would be the best way to write this function (not in terms of speed but in readability)
I was reading this, but I have trouble to apply it to my problem.
You can't hash lists as dictionary values. So go other way round. Create a mapping of type -> list. And initialize your output with the default type. This allows you to keep on adding new types to your mapping without changing any code.
def very_long_func():
something = 'Audi'
car = ['VW', 'Audi', 'BMW']
drinks = ['Cola', 'Fanta', 'Pepsi']
countries = ['France', 'Germany', 'Italy']
out = {'type': 'nothing found'} # If nothing matches
mapping = {
'car brand': car,
'lemonade brand': drinks,
'country': countries
}
for k,v in mapping.items() :
if something in v:
out['type'] = k # update if match found
break
return out # returns matched or default value
you can create dictionary like this and then use map_dict.
from functools import reduce
car = ['VW', 'Audi', 'BMW']
drinks = ['Cola', 'Fanta', 'Pepsi']
countries = ['France', 'Germany', 'Italy']
li = [car, drinks, countries]
types = ['car brand', 'lemonade brand', 'country', 'nothing found']
dl = [dict(zip(l, [types[idx]]*len(l))) for idx, l in enumerate(li)]
map_dict = reduce(lambda a, b: dict(a, **b), dl)
Try this:
def create_dct(lst, flag):
return {k:flag for k in lst}
car = ['VW', 'Audi', 'BMW']
drinks = ['Cola', 'Fanta', 'Pepsi']
countries = ['France', 'Germany', 'Italy']
merge_dcts = {}
merge_dcts.update(create_dct(car, 'car brand'))
merge_dcts.update(create_dct(drinks, 'lemonade brand'))
merge_dcts.update(create_dct(countries, 'country'))
something = 'Audi'
try:
print("type: ", merge_dcts[something])
except:
print("type: nothing found")
You can simulate a switch statement with a helper function like this:
def switch(v): yield lambda *c: v in c
The your code could be written like this:
something = 'Audi'
for case in switch(something):
if case('VW', 'Audi', 'BMW'): name = 'car brand' ; break
if case('Cola', 'Fanta', 'Pepsi'): name = 'lemonade brand' ; break
if case('France', 'Germany', 'Italy'): name = 'country' ; break
else: name = 'nothing found'
return {'type':name}
If you don't have specific code to do for each value, then a simple mapping dictionary would probably suffice. For ease of maintenance, you can start with a category-list:type-name mapping and expand it before use:
mapping = { ('VW', 'Audi', 'BMW'):'car brand',
('Cola', 'Fanta', 'Pepsi'):'lemonade brand',
('France', 'Germany', 'Italy'):'country' }
mapping = { categ:name for categs,name in mapping.items() for categ in categs }
Then your code will look like this:
something = 'Audi'
return {'type':mapping.get(something,'nothing found')}
using a defaultdict would make this even simpler to use by providing the 'nothing found' value automatically so you could write: return {'type':mapping[something]}

Selecting dictionary nested in a list by index

Here is am example of the list:
{'Item': 'milk', 'Price': '2.0', 'Quantity': '2'}, {'Item': 'egg', 'Price': '12.0', 'Quantity': '1'}]
Here is my code:
def edit_items(info):
xy = info
print('Index | Orders')
for x in enumerate(xy):
print('\n')
print(x)
choice = int(input('Which entry would you like to edit? Choose by index. :'))
print(x[choice])
Id like the user to able to chose an entry by index, and allow them to edit information inside the dictionary.
So far my code prints out:
Index | Orders
(0, {'Item': 'milk', 'Price': '2.0', 'Quantity': '2'})
(1, {'Item': 'egg', 'Price': '12.0', 'Quantity': '1'})
But i have no idea how to choose one, assign It to a variable and carry out the ability to edit whats inside.
Cheers. Nalpak_
def edit_items(info):
xy = info
# to make multiple edits.
while True:
print('Index | Orders')
for x in range(len(xy)):
print(x,xy[x])
choice = int(input('Which entry would you like to edit?\nChoose by index: '))
print(xy[choice])
edit = input('What you want to edit: ') # Key val of dict
value = input("Enter: ") # Value for the specific key in dict
xy[choice][edit] = value
print('list updated.')
print(xy[choice])
more_edits = input('\nDo you want to make more edits?(y/n): ')
if more_edits == 'n':
break
edit_items(info)
this will help you make multiple edits.
If you want to edit an item in a dictionary, you can easily do it by accessing it by the key.
First, we set up the data
xy = [{'Item': 'milk', 'Price': '2.0', 'Quantity': '2'}, {'Item': 'egg', 'Price': '12.0', 'Quantity': '1'}]
Then if I understood you correctly, this edit_items method should do exactly what you need:
def edit_items(i):
name = input('Type in a new item name: ')
xy[i]['Item'] = name # 'Item' is the key.
Everything else is pretty much the same:
print('Index | Orders')
for x in enumerate(xy):
print('\n')
print(x)
choice = int(input('Which entry would you like to edit? Choose by index. :'))
print(xy[choice])
edit_items(choice)
print(xy)
If you want, you can also use input for getting a key (property) of an item you want to edit.

Recursive dictionary from list of hierarchical codes

I have a list of (around 100) values like this list:
list = ['40201020', '45102020', '25203020', '20106020', '25301020', '40402030', '20202010']
I need a dictionary that
a) lists all parents for each value. The parent has one digit less (from the right):
child = '40201020'
parent = '4020102'
this format would be ideal:
dict['4020102parent'] = '40201020'
b) I need all parents of parents up to one remaining digit. So parent '4020102' gets this parent:
dict['4020102parent"] = '402010'
and
dict['402010parent"] = '40201'
etc.
c) I then need all last descendents for each parent as a list. By last descendant, I mean the 8 digit codes of the original list. So the digit '4' would have these codes:
dict['4children'] = ['40201020', '45102020', '40402030']
or:
dict['40children'] = ['40201020', '40402030']
Will your list always contain strings and is a dictionary a requirement? If you will always be working with strings and you are just wanting a way to find parents and children, I would suggest using python's string handling capabilities. You could define functions parent and children like this:
def parent(list_item):
return list_item[:-1]
def children(my_list, parent_str):
children_found = []
for item in my_list:
if item.startswith(parent_str)
children_found.append(item)
return children_found
Then calling parent('40201020') would produce '4020102' and calling children(my_list, '40') would produce ['40201020', '40402030']. You can call parent recursively to get a string with one less item each time.
I am still confused why you need parent dict when you can just store recursive result in a list and can use str.startswith() method :
still i have stored parentdict in dict_data you can use that :
list1 = ['40201020', '45102020', '25203020', '20106020', '25301020', '40402030', '20202010']
dict_data=[]
track=[]
list_2={}
def main_function(lst_1):
for i in lst_1:
def recursive(lst):
parent = {}
if not lst:
return 0
else:
parent[lst[:-1] + 'parent'] = lst
track.append(lst)
dict_data.append(parent)
return recursive(lst[:-1])
recursive(i)
main_function(list1)
for recursive_unit in set(track):
for items in list1:
if items.startswith(recursive_unit):
if recursive_unit not in list_2:
list_2[recursive_unit]=[items]
else:
list_2[recursive_unit].append(items)
print(list_2)
output:
{'25203': ['25203020'], '25': ['25203020', '25301020'],'4': ['40201020', '45102020', '40402030'],'4510': ['45102020'], '2520302': ['25203020'], '40402030': ['40402030'], '2010602': ['20106020'], '45102020': ['45102020'], '45': ['45102020'], '253010': ['25301020'], '4020': ['40201020'], '252': ['25203020'], '20202010': ['20202010'], '20106': ['20106020'], '201060': ['20106020'],'202020': ['20202010'], '2530102': ['25301020'], '402': ['40201020'], '2010': ['20106020'], '4510202': ['45102020'], '2530': ['25301020'], '451020': ['45102020'], '2020201': ['20202010'], '404020': ['40402030'], '25203020': ['25203020'], '2': ['25203020', '20106020', '25301020', '20202010'], '20202': ['20202010'], '253': ['25301020'], '40402': ['40402030'], '451': ['45102020'], '40201020': ['40201020'], '252030': ['25203020'], '2520': ['25203020'], '40': ['40201020', '40402030'], '4040': ['40402030'], '402010': ['40201020'], '4020102': ['40201020'], '25301020': ['25301020'], '20106020': ['20106020'], '201': ['20106020'], '20': ['20106020', '20202010'], '202': ['20202010'], '40201': ['40201020'], '45102': ['45102020'], '2020': ['20202010'], '25301': ['25301020'], '4040203': ['40402030'], '404': ['40402030']}
As stated in my comment, a dictionary where each key contains the children seems like a more reasonable idea.
To achieve this, we can loop through each element in your list (which I renamed to l as to not override the built-in list() function), and append this value to the lists of all its parents in a dictionary, d.
The code for the above described method would look something along the lines of:
d = {}
for i in l:
for e in range(1, len(l)-1):
d.setdefault(i[:e], []).append(i)
which will then allow you to do things like:
>>> d['4']
['40201020', '45102020', '40402030']
>>> d['40']
['40201020', '40402030']
>>> d['25']
['25203020', '25301020']
# do not use "list" as variable name (it's a python builtin)
mylist = ['2', '20', '201', '2010', '20106', '201060', '2010602']
mylist_sorted = mylist.clone()
mylist_sorted.sort()
parents = {}
children = {}
for element in mylist_sorted:
parents[e] = []
children[e] = []
for subelement in [element[0:x] for x in range(1,len(element))]:
if subelement in parents:
parents[element].append(subelement)
children[subelement].append(element)
print(parents)
print(children)
Output:
{'201060': ['2', '20', '201', '2010', '20106'],
'20106': ['2', '20', '201', '2010'],
'2010': ['2', '20', '201'],
'201': ['2', '20'],
'2': [],
'2010602': ['2', '20', '201', '2010', '20106', '201060'],
'20': ['2']}
{'201060': ['2010602'],
'20106': ['201060', '2010602'],
'2010': ['20106', '201060', '2010602'],
'201': ['2010', '20106', '201060', '2010602'],
'2': ['20', '201', '2010', '20106', '201060', '2010602'],
'2010602': [],
'20': ['201', '2010', '20106', '201060', '2010602']}

Sort a specific column from a text file

I am trying to sort a column from a .txt file. I am going do a point-register system where I am going to save the name of the player and his three laps.
I am saving my values in the text-file like so:
1. name;lap_1;lap_2;lap_3;
2. name;lap_1;lap_2;lap_3;
3. name;lap_1;lap_2;lap_3;
In my code I write them to the file like so:
for result in results:
my_file.write("{}:{}:{}:{}:{}:{};\n".format(result["name"],
result["lap1"],
result["lap2"],
result["lap3"],
result["total"],
result["average"]))
How do I sort each column, for example "name"? And how do I print it out?
first of all, as #sgrg suggested, use CSV-file format, e. g. we can write simply with
import csv
def write_results(results, fields_names):
# or use mode="a" if you want to append
with open("my_file.csv", mode="w", newline="") as my_file:
csv_writer = csv.DictWriter(my_file, fieldnames=fields_names, delimiter=";")
# remember: you don"t need to add headers in "append" mode
csv_writer.writeheader()
for result in results:
csv_writer.writerow(result)
then read with
def read_results(fields_names):
with open("my_file.csv", mode="r") as my_file:
# ignoring headers
next(my_file)
csv_reader = csv.DictReader(my_file, fieldnames=fields_names, delimiter=";")
return list(csv_reader)
sorting of results by name can be done with
sorted_results = sorted(results, key=lambda result: result["name"])
usage
fields_names = ["name", "lap1", "lap2", "lap3", "total", "avarage"]
results_tuples = [("Luke", "lap1_value", "lap2_value", "lap3_value", 100, 96.3),
("Stephen", "lap1_value", "lap2_value", "lap3_value", 100, 96.3),
("Adrian", "lap1_value", "lap2_value", "lap3_value", 100, 96.3)]
results = [dict(zip(fields_names, result_tuple)) for result_tuple in results_tuples]
write_results(results,
fields_names=fields_names)
results = read_results(fields_names)
sorted_results = sorted(results, key=lambda result: result["name"])
in given example results is a list object which looks like
[{'avarage': 96.3,
'lap1': 'lap1_value',
'lap2': 'lap2_value',
'lap3': 'lap3_value',
'name': 'Luke',
'total': 100},
{'avarage': 96.3,
'lap1': 'lap1_value',
'lap2': 'lap2_value',
'lap3': 'lap3_value',
'name': 'Stephen',
'total': 100},
{'avarage': 96.3,
'lap1': 'lap1_value',
'lap2': 'lap2_value',
'lap3': 'lap3_value',
'name': 'Adrian',
'total': 100}]
and sorted_results is a list object which looks like
[OrderedDict([('name', 'Adrian'),
('lap1', 'lap1_value'),
('lap2', 'lap2_value'),
('lap3', 'lap3_value'),
('total', '100'),
('avarage', '96.3')]),
OrderedDict([('name', 'Luke'),
('lap1', 'lap1_value'),
('lap2', 'lap2_value'),
('lap3', 'lap3_value'),
('total', '100'),
('avarage', '96.3')]),
OrderedDict([('name', 'Stephen'),
('lap1', 'lap1_value'),
('lap2', 'lap2_value'),
('lap3', 'lap3_value'),
('total', '100'),
('avarage', '96.3')])]
More info about csv module at docs
More info about OrderedDict at docs

issue in list of dict

class MyOwnClass:
# list who contains the queries
queries = []
# a template dict
template_query = {}
template_query['name'] = 'mat'
template_query['age'] = '12'
obj = MyOwnClass()
query = obj.template_query
query['name'] = 'sam'
query['age'] = '23'
obj.queries.append(query)
query2 = obj.template_query
query2['name'] = 'dj'
query2['age'] = '19'
obj.queries.append(query2)
print obj.queries
It gives me
[{'age': '19', 'name': 'dj'}, {'age': '19', 'name': 'dj'}]
while I expect to have
[{'age': '23' , 'name': 'sam'}, {'age': '19', 'name': 'dj'}]
I thought to use a template for this list because I'm gonna to use it very often and there are some default variable who does not need to be changed.
Why does doing it the template_query itself changes? I'm new to python and I'm getting pretty confused.
this is because you are pointing to the same dictionary each time ... and overwriting the keys ...
# query = obj.template_query - dont need this
query = {}
query['name'] = 'sam'
query['age'] = '23'
obj.queries.append(query)
query2 = {} #obj.template_query-dont need this
query2['name'] = 'dj'
query2['age'] = '19'
obj.queries.append(query2)
this should demonstrate your problem
>>> q = {'a':1}
>>> lst = []
>>> lst.append(q)
>>> q['a']=2
>>> lst
[{'a': 2}]
>>> lst.append(q)
>>> lst
[{'a': 2}, {'a': 2}]
you could implement your class differently
class MyOwnClass:
# a template dict
#property
def template_query():
return {'name':'default','age':-1}
this will make obj.template_query return a new dict each time
This is because query and query2 are both referring to the same object. obj.template_query, in this case.
Better to make a template factory:
def template_query(**kwargs):
template = {'name': 'some default value',
'age': 'another default value',
'car': 'generic car name'}
template.update(**kwargs)
return template
That creates a new dictionary every time it's called. So you can do:
>>> my_query = template_query(name="sam")
>>> my_query
{'name': 'sam', 'age': 'another default value', 'car': 'generic car name'}
You're copying the same dict into query2. Instead, you might want to create the needed dict by creating a function template_query() and constructing a new dict each time:
class MyOwnClass:
# a template dict
def template_query():
d = {}
d['name'] = 'mat'
d['age'] = '12'
d['car'] = 'ferrari'
return d

Categories