-
-
Save m0xb/045ae6e510893a225f623a505ec0c24b to your computer and use it in GitHub Desktop.
| [ | |
| { | |
| "id": "0001", | |
| "type": "donut", | |
| "name": "Cake", | |
| "ppu": 0.55, | |
| "batters": | |
| { | |
| "batter": | |
| [ | |
| { "id": "1001", "type": "Regular" }, | |
| { "id": "1002", "type": "Chocolate" }, | |
| { "id": "1003", "type": "Blueberry" }, | |
| { "id": "1004", "type": "Devil's Food" } | |
| ] | |
| }, | |
| "topping": | |
| [ | |
| { "id": "5001", "type": "None" }, | |
| { "id": "5002", "type": "Glazed" }, | |
| { "id": "5005", "type": "Sugar" }, | |
| { "id": "5007", "type": "Powdered Sugar" }, | |
| { "id": "5006", "type": "Chocolate with Sprinkles" }, | |
| { "id": "5003", "type": "Chocolate" }, | |
| { "id": "5004", "type": "Maple" } | |
| ] | |
| }, | |
| { | |
| "id": "0002", | |
| "type": "donut", | |
| "name": "Raised", | |
| "ppu": 0.55, | |
| "batters": | |
| { | |
| "batter": | |
| [ | |
| { "id": "1001", "type": "Regular" } | |
| ] | |
| }, | |
| "topping": | |
| [ | |
| { "id": "5001", "type": "None" }, | |
| { "id": "5002", "type": "Glazed" }, | |
| { "id": "5005", "type": "Sugar" }, | |
| { "id": "5003", "type": "Chocolate" }, | |
| { "id": "5004", "type": "Maple" } | |
| ] | |
| }, | |
| { | |
| "id": "0003", | |
| "type": "donut", | |
| "name": "Old Fashioned", | |
| "ppu": 0.55, | |
| "batters": | |
| { | |
| "batter": | |
| [ | |
| { "id": "1001", "type": "Regular" }, | |
| { "id": "1002", "type": "Chocolate" } | |
| ] | |
| }, | |
| "topping": | |
| [ | |
| { "id": "5001", "type": "None" }, | |
| { "id": "5002", "type": "Glazed" }, | |
| { "id": "5003", "type": "Chocolate" }, | |
| { "id": "5004", "type": "Maple" } | |
| ] | |
| } | |
| ] |
import json
with open('donuts.json', 'r') as fp:
donutjson = json.load(fp)
#Task 1
def find_id_of_donut_from_name(donuts, target):
for donut in donuts:
if donut['name'] == target:
return donut['id']
id = find_id_of_donut_from_name(donutjson, 'Raised')
print(id)
#Task 2
def find_ppu_of_donut_from_name(donuts, target):
for donut in donuts:
if donut['name'] == target:
return donut['ppu']
ppu = find_ppu_of_donut_from_name(donutjson, 'Old Fashioned')
print(ppu)
#Task 3
def find_num_toppings_from_id(donuts, target):
for donut in donuts:
if donut['id'] == target:
return len(donut['topping'])
toppingcount = find_num_toppings_from_id(donutjson, '0003')
print(toppingcount)
#Task 4
def find_num_batters_from_id(donuts, target):
for donut in donuts:
if donut['id'] == target:
return len(donut['batters']['batter'])
battercount = find_num_batters_from_id(donutjson, '0001')
print(battercount)
#Task 5
def find_topping_type_from_donut_name(donuts, target, toppingid):
for donut in donuts:
if donut['name'] == target:
for topping in donut['topping']:
if topping['id'] == toppingid:
return topping['type']
print(find_topping_type_from_donut_name(donutjson, 'Raised', '5003'))
>>> with open('donuts.json', 'r') as fp:
... donuts = json.load(fp)
...
>>>
>>> [m[0] for i,m in enumerate([m for m in [[t['type'] for t in d['topping'] if t['id'] == '5003'] for d in donuts] if m]) if i == 0][0]
'Chocolate'
This is the original expression and the result of evaluating it on the example in this gist:
>>> [m[0] for i,m in enumerate([m for m in [[t['type'] for t in d['topping'] if t['id'] == '5003'] for d in donuts] if m]) if i == 0][0]
'Chocolate'
Let's break it down. Work from the innermost sub-expression to the outermost.
The smallest valid sub-expression is still a bit unwieldy:
>>> [[t['type'] for t in d['topping'] if t['id'] == '5003'] for d in donuts]
[['Chocolate'], ['Chocolate'], ['Chocolate']]
You can see that it returns a list of lists. Each list has exactly one element, the string "Chocolate".
The expression above is two list comprehensions. The inner one, [t['type'] for t in d['topping'] if t['id'] == '5003'] depends on the variable d which is bound to an element from donuts in the outer list comprehension. So d represents a single donut object from the JSON. Given d (representing a donut), the inner list comprehension iterates over each topping, bound to t, in the donut (d['topping'], the JSON has the quirk of this property not being plural, but it's still a list), uses the if to filter out toppings that do not have the id 5003, and generates a new list with the type of each topping that wasn't filtered.
Note that we've basically got the answer already, but we need to clean it up, since it's a list of lists.
In this particular example, we could just append [0][0] to the expression to access the first element of the first list:
>>> [[t['type'] for t in d['topping'] if t['id'] == '5003'] for d in donuts][0][0]
'Chocolate'
But, that's not good because we're making assumptions (in this case, that the first donut has the topping we want).
This doesn't work (suppose we were looking for 5005 instead of 5003:
>>> [[t['type'] for t in d['topping'] if t['id'] == '5005'] for d in donuts]
[[], ['Sugar'], ['Sugar']]
>>> [[t['type'] for t in d['topping'] if t['id'] == '5005'] for d in donuts][0][0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
So, we need to a filter to ensure there are no empty lists:
>>> [m for m in [[t['type'] for t in d['topping'] if t['id'] == '5005'] for d in donuts] if m]
[['Sugar'], ['Sugar']]
In the above, m is bound to an element from the middle list comprehension, which is a list of topping types. Note that no elements get filtered out in the case for 5003, but that's okay, the point is to be safe:
>>> [m for m in [[t['type'] for t in d['topping'] if t['id'] == '5003'] for d in donuts] if m]
[['Chocolate'], ['Chocolate'], ['Chocolate']]
We still have a list of list of strings, when we just want a string. We know all the lists are the same, so let's get the first one. Again, we could append [0] to the expression, but we want to avoid assumptions when possible.
We pass our current expression to enumerate which produces a list of tuples:
>>> list(enumerate([[t['type'] for t in d['topping'] if t['id'] == '5005'] for d in donuts]))
[(0, ['Sugar']), (1, ['Sugar']), (2, [])]
Now, we can easily filter out all elements that aren't the first with another list comprehension:
>>> [m[0] for index_in_list,m in enumerate([m for m in [[topping['type'] for topping in donut['topping'] if topping['id'] == '5003'] for donut in donuts] if m]) if index_in_list == 0]
['Chocolate']
Finally, we have a one-element list. In my solution, I made an assumption that it had at least one element, and just appended [0] to the end:
>>> [m[0] for index_in_list,m in enumerate([m for m in [[topping['type'] for topping in donut['topping'] if topping['id'] == '5003'] for donut in donuts] if m]) if index_in_list == 0][0]
'Chocolate'
Which works in this case. But, would error if there were not toppings with id 5003. To make it not error out, I could do:
>>> ''.join([m[0] for index_in_list,m in enumerate([m for m in [[topping['type'] for topping in donut['topping'] if topping['id'] == '5003'] for donut in donuts] if m]) if index_in_list == 0])
'Chocolate'
To prove that the second one works for an invalid id while the first one does not:
>>> ''.join([m[0] for index_in_list,m in enumerate([m for m in [[topping['type'] for topping in donut['topping'] if topping['id'] == '999999'] for donut in donuts] if m]) if index_in_list == 0])
''
>>> [m[0] for index_in_list,m in enumerate([m for m in [[topping['type'] for topping in donut['topping'] if topping['id'] == '999999'] for donut in donuts] if m]) if index_in_list == 0][0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
#Task 1.1
task1_1 = [donut['id'] for donut in donutjson if donut['name'] == 'Raised'][0]
print(task1_1)
#Task 2.1
task2_1 = [donut['ppu'] for donut in donutjson if donut['name'] == 'Old Fashioned'][0]
print(task2_1)
#Task 3.1
print(len([donut['topping'] for donut in donutjson if donut['id'] == '0003'][0]))
#Task 4.1
print(len([donut['batters']['batter'] for donut in donutjson if donut['id'] == '0001'][0]))
#Task 5.1
print([[topping['type'] for topping in donut['topping'] if topping['id'] == '5003'] for donut in donutjson][0][0])
Exercises:
Write an expression for each of the following:
idproperty of the "Raised" donut. Expected:"0002"ppuproperty of the "Old Fashioned" donut. Expected:0.55id = "0003". Expected:4id = "0001". Expected:4typeproperty of topping withid = "5003". Expected:"Chocolate"(Hint: this one is in multiple places, choose any one)