Skip to content

Instantly share code, notes, and snippets.

@m0xb
Last active October 3, 2017 08:09
Show Gist options
  • Select an option

  • Save m0xb/045ae6e510893a225f623a505ec0c24b to your computer and use it in GitHub Desktop.

Select an option

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" }
]
}
]
@m0xb
Copy link
Author

m0xb commented Sep 26, 2017

Exercises:

Write an expression for each of the following:

  1. Get the id property of the "Raised" donut. Expected: "0002"
  2. Get the ppu property of the "Old Fashioned" donut. Expected: 0.55
  3. Get the number of possible toppings for donut with id = "0003". Expected: 4
  4. Get the number of possible batters for donut with id = "0001". Expected: 4
  5. Get the type property of topping with id = "5003". Expected: "Chocolate" (Hint: this one is in multiple places, choose any one)

@WorryingWonton
Copy link

WorryingWonton commented Sep 28, 2017

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'))

@m0xb
Copy link
Author

m0xb commented Sep 28, 2017

>>> 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'

@m0xb
Copy link
Author

m0xb commented Sep 30, 2017

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

@WorryingWonton
Copy link

WorryingWonton commented Oct 3, 2017

#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])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment