Skip to content

Commit 1cba1bd

Browse files
committed
format
1 parent db48b77 commit 1cba1bd

6 files changed

Lines changed: 184 additions & 7192 deletions

File tree

book.md

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4700,7 +4700,7 @@ If you give me 27 cents, I can give you:
47004700
64 """
47014701
65
47024702
66 nickels, dimes, quarters = coins
4703-
67 big_coins = (5 * nickels) + (10 * dimes) + (25 * quarters)
4703+
67 big_coins = sum([5 * nickels, 10 * dimes, 25 * quarters])
47044704
68
47054705
69 if big_coins <= value:
47064706
70 return (quarters, dimes, nickels, value - big_coins)
@@ -4713,6 +4713,97 @@ If you give me 27 cents, I can give you:
47134713

47144714
\newpage
47154715

4716+
## Discussion
4717+
4718+
Let's start with a short look at `get_args` where I've decided to move the validation of the single `value` argument into this function rather than getting the arguments in `main` and checking there. We can use `argparse` to ensure the user provides an `int` value, but there's no `type` to say that it must be in our desired range; however, I can use the `parser.error` function on line 22 to trigger the normal fail-with-usage behaviour we normally get from `argparse`. From the standpoint of the calling code on line 32, all the work to coerce and validate the user happens in `get_args`. If we make it past line 32, then all must have been good and we can just focus on the task at hand.
4719+
4720+
I'd like to mention that I worked for a couple of days on this solution. I tried many different approaches before settling on the way I solved this problem, so what I do next may not be at all how you solved the problem. My idea was to find how many possible nickels, dimes, and quarters are in the given `value` and then find every combination of those values to see which ones sum to the `value` or less. To do this, I can use the `//` operator to find the integer division of the `value` by each of 5, 10, and 25 for nickels, dimes, and quarters, e.g.:
4721+
4722+
````
4723+
>>> value = 13
4724+
>>> value // 5
4725+
2
4726+
````
4727+
4728+
Finds there are two nickels in 13 cents. I construct a range that includes 0, 1, and 2 like so:
4729+
4730+
````
4731+
>>> nickels = range((value // 5) + 1)
4732+
>>> nickels
4733+
range(0, 3)
4734+
>>> list(nickels)
4735+
[0, 1, 2]
4736+
````
4737+
4738+
I used the `itertools.product` function and three ranges for nickels, dimes, and quarters to find every possible combination of every number of coins
4739+
4740+
````
4741+
>>> dimes = range((value // 10) + 1)
4742+
>>> quarters = range((value // 25) + 1)
4743+
>>> from itertools import product
4744+
>>> list(product(nickels, dimes, quarters))
4745+
[(0, 0, 0), (0, 1, 0), (1, 0, 0), (1, 1, 0), (2, 0, 0), (2, 1, 0)]
4746+
````
4747+
4748+
I want to include 0 of every coin so that I can make up the remainder in pennies. Let's jump ahead to the `figure` function to see how I wanted to use these values. Because `product` gives me a list of 3-tuples, I decided to pass `figure` the `value` and then a `coins` tuple that I unpack on line 66. I `sum` the values of the `nickels`, `dimes`, and `quarters` on line 67 and see if that is less than or equal to the `value`. If so, I get the number of pennies by subtracting the sum of the larger coins and return a 4-tuple with the number of each coin. If the previous sum was larger than the `value`, we don't bother defining the `return` of the function and so `None` is used.
4749+
4750+
Going back to line 37 where I want to call `figure` for each of the combinations returned by `product`, I use a list comprehension combined with a `map` which may seem rather dense but works quite well. The `map` wants a function and a list of items to apply the function. There's a slight problem in that the `figure` function wants 2 arguments -- the `value` and the 3-tuple. I could have written the `map` using a `lambda`:
4751+
4752+
````
4753+
>>> def figure(value, coins):
4754+
... nickels, dimes, quarters = coins
4755+
... big_coins = sum([5 * nickels, 10 * dimes, 25 * quarters])
4756+
... if big_coins <= value:
4757+
... return (quarters, dimes, nickels, value - big_coins)
4758+
...
4759+
>>> list(map(lambda c: figure(value, c), product(nickels, dimes, quarters)))
4760+
[(0, 0, 0, 13), (0, 1, 0, 3), (0, 0, 1, 8), None, (0, 0, 2, 3), None]
4761+
````
4762+
4763+
But I thought it would be cleaner to create a partial application of the `figure` function with the `value` already bound. The `functools.partial` is exactly the tool we need and then we only need to pass in the 3-tuple of the coins:
4764+
4765+
````
4766+
>>> from functools import partial
4767+
>>> fig = partial(figure, value)
4768+
>>> fig((1,0,0))
4769+
(0, 0, 1, 8)
4770+
````
4771+
4772+
And so now I can use this `partial` function in my `map`:
4773+
4774+
````
4775+
>>> list(map(fig, product(nickels, dimes, quarters)))
4776+
[(0, 0, 0, 13), (0, 1, 0, 3), (0, 0, 1, 8), None, (0, 0, 2, 3), None]
4777+
````
4778+
4779+
Notice how we get some `None` values returned. Remember, this is because some of the combinations we are trying are too large, e.g., the maximum number of all the coins will be too large. So, to filter out those value, I can use a list comprehension with a guard at the end:
4780+
4781+
````
4782+
>>> combos = [c for c in map(fig, product(nickels, dimes, quarters)) if c]
4783+
>>> combos
4784+
[(0, 0, 0, 13), (0, 1, 0, 3), (0, 0, 1, 8), (0, 0, 2, 3)]
4785+
````
4786+
4787+
I could have used a `filter` for this, but it just doesn't seem to read as well:
4788+
4789+
````
4790+
>>> list(filter(lambda c: c, map(fig, product(nickels, dimes, quarters))))
4791+
[(0, 0, 0, 13), (0, 1, 0, 3), (0, 0, 1, 8), (0, 0, 2, 3)]
4792+
````
4793+
4794+
This is a list of 4-tuples representing the number of quarters, dimes, nickels, and pennies that will sum to `13`. We still need to report back to the user, so that is the purpose of the `fmt_combo` function. Given that 4-tuple, I want to report, e.g., "1 quarter" or "3 dimes", so I need to know the value of the denomination and the singular/plural versions of name of the denomination. I use the `zip` function to pair the coin denominations with their values:
4795+
4796+
````
4797+
>>> combo = (0, 0, 0, 13)
4798+
>>> list(zip(('quarter', 'dime', 'nickel', 'penny'), combo))
4799+
[('quarter', 0), ('dime', 0), ('nickel', 0), ('penny', 13)]
4800+
````
4801+
4802+
The `plural` version of each name is made by adding `s` except for `penny`, so line 53 handles that. If the denomination is not in the `combo` (e.g., here we have only pennies), then we skip those by using `if val` where `val` will be the number of coins. The integer value `0` will evaluate to `False` in a Boolean context, so only those with a non-zero value will be included. I decided to create a `list` of the strings for each denomination, so I `append` to that list the `val` plus the correct singular or plural version of the name, finally returning that list joined on comma-space (`', '`).
4803+
4804+
Finally lines 39-43 are left to formatting the report to the user, being sure to provide feedback that includes the original `value` ("If you give me ...") and an enumerated list of all the possible ways we could make change. The test suite does not bother to check the order in which you return the combinations, only that the correct number are present and they are in the correct format.
4805+
\newpage
4806+
47164807
# Chapter 34: Markov Chain
47174808

47184809
Write a Python program called `markov.py` that takes one or more text files as positional arguments for training. Use the `-n|--num_words` argument (default `2`) to find clusters of words and the words that follow them, e.g., in "The Bustle" by Emily Dickinson:

first_bank_of_change/discussion.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
Let's start with a short look at `get_args` where I've decided to move the validation of the single `value` argument into this function rather than getting the arguments in `main` and checking there. We can use `argparse` to ensure the user provides an `int` value, but there's no `type` to say that it must be in our desired range; however, I can use the `parser.error` function on line 22 to trigger the normal fail-with-usage behaviour we normally get from `argparse`. From the standpoint of the calling code on line 32, all the work to coerce and validate the user happens in `get_args`. If we make it past line 32, then all must have been good and we can just focus on the task at hand.
2+
3+
I'd like to mention that I worked for a couple of days on this solution. I tried many different approaches before settling on the way I solved this problem, so what I do next may not be at all how you solved the problem. My idea was to find how many possible nickels, dimes, and quarters are in the given `value` and then find every combination of those values to see which ones sum to the `value` or less. To do this, I can use the `//` operator to find the integer division of the `value` by each of 5, 10, and 25 for nickels, dimes, and quarters, e.g.:
4+
5+
````
6+
>>> value = 13
7+
>>> value // 5
8+
2
9+
````
10+
11+
Finds there are two nickels in 13 cents. I construct a range that includes 0, 1, and 2 like so:
12+
13+
````
14+
>>> nickels = range((value // 5) + 1)
15+
>>> nickels
16+
range(0, 3)
17+
>>> list(nickels)
18+
[0, 1, 2]
19+
````
20+
21+
I used the `itertools.product` function and three ranges for nickels, dimes, and quarters to find every possible combination of every number of coins
22+
23+
````
24+
>>> dimes = range((value // 10) + 1)
25+
>>> quarters = range((value // 25) + 1)
26+
>>> from itertools import product
27+
>>> list(product(nickels, dimes, quarters))
28+
[(0, 0, 0), (0, 1, 0), (1, 0, 0), (1, 1, 0), (2, 0, 0), (2, 1, 0)]
29+
````
30+
31+
I want to include 0 of every coin so that I can make up the remainder in pennies. Let's jump ahead to the `figure` function to see how I wanted to use these values. Because `product` gives me a list of 3-tuples, I decided to pass `figure` the `value` and then a `coins` tuple that I unpack on line 66. I `sum` the values of the `nickels`, `dimes`, and `quarters` on line 67 and see if that is less than or equal to the `value`. If so, I get the number of pennies by subtracting the sum of the larger coins and return a 4-tuple with the number of each coin. If the previous sum was larger than the `value`, we don't bother defining the `return` of the function and so `None` is used.
32+
33+
Going back to line 37 where I want to call `figure` for each of the combinations returned by `product`, I use a list comprehension combined with a `map` which may seem rather dense but works quite well. The `map` wants a function and a list of items to apply the function. There's a slight problem in that the `figure` function wants 2 arguments -- the `value` and the 3-tuple. I could have written the `map` using a `lambda`:
34+
35+
````
36+
>>> def figure(value, coins):
37+
... nickels, dimes, quarters = coins
38+
... big_coins = sum([5 * nickels, 10 * dimes, 25 * quarters])
39+
... if big_coins <= value:
40+
... return (quarters, dimes, nickels, value - big_coins)
41+
...
42+
>>> list(map(lambda c: figure(value, c), product(nickels, dimes, quarters)))
43+
[(0, 0, 0, 13), (0, 1, 0, 3), (0, 0, 1, 8), None, (0, 0, 2, 3), None]
44+
````
45+
46+
But I thought it would be cleaner to create a partial application of the `figure` function with the `value` already bound. The `functools.partial` is exactly the tool we need and then we only need to pass in the 3-tuple of the coins:
47+
48+
````
49+
>>> from functools import partial
50+
>>> fig = partial(figure, value)
51+
>>> fig((1,0,0))
52+
(0, 0, 1, 8)
53+
````
54+
55+
And so now I can use this `partial` function in my `map`:
56+
57+
````
58+
>>> list(map(fig, product(nickels, dimes, quarters)))
59+
[(0, 0, 0, 13), (0, 1, 0, 3), (0, 0, 1, 8), None, (0, 0, 2, 3), None]
60+
````
61+
62+
Notice how we get some `None` values returned. Remember, this is because some of the combinations we are trying are too large, e.g., the maximum number of all the coins will be too large. So, to filter out those value, I can use a list comprehension with a guard at the end:
63+
64+
````
65+
>>> combos = [c for c in map(fig, product(nickels, dimes, quarters)) if c]
66+
>>> combos
67+
[(0, 0, 0, 13), (0, 1, 0, 3), (0, 0, 1, 8), (0, 0, 2, 3)]
68+
````
69+
70+
I could have used a `filter` for this, but it just doesn't seem to read as well:
71+
72+
````
73+
>>> list(filter(lambda c: c, map(fig, product(nickels, dimes, quarters))))
74+
[(0, 0, 0, 13), (0, 1, 0, 3), (0, 0, 1, 8), (0, 0, 2, 3)]
75+
````
76+
77+
This is a list of 4-tuples representing the number of quarters, dimes, nickels, and pennies that will sum to `13`. We still need to report back to the user, so that is the purpose of the `fmt_combo` function. Given that 4-tuple, I want to report, e.g., "1 quarter" or "3 dimes", so I need to know the value of the denomination and the singular/plural versions of name of the denomination. I use the `zip` function to pair the coin denominations with their values:
78+
79+
````
80+
>>> combo = (0, 0, 0, 13)
81+
>>> list(zip(('quarter', 'dime', 'nickel', 'penny'), combo))
82+
[('quarter', 0), ('dime', 0), ('nickel', 0), ('penny', 13)]
83+
````
84+
85+
The `plural` version of each name is made by adding `s` except for `penny`, so line 53 handles that. If the denomination is not in the `combo` (e.g., here we have only pennies), then we skip those by using `if val` where `val` will be the number of coins. The integer value `0` will evaluate to `False` in a Boolean context, so only those with a non-zero value will be included. I decided to create a `list` of the strings for each denomination, so I `append` to that list the `val` plus the correct singular or plural version of the name, finally returning that list joined on comma-space (`', '`).
86+
87+
Finally lines 39-43 are left to formatting the report to the user, being sure to provide feedback that includes the original `value` ("If you give me ...") and an enumerated list of all the possible ways we could make change. The test suite does not bother to check the order in which you return the combinations, only that the correct number are present and they are in the correct format.

first_bank_of_change/solution.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def figure(value, coins):
6464
"""
6565

6666
nickels, dimes, quarters = coins
67-
big_coins = (5 * nickels) + (10 * dimes) + (25 * quarters)
67+
big_coins = sum([5 * nickels, 10 * dimes, 25 * quarters])
6868

6969
if big_coins <= value:
7070
return (quarters, dimes, nickels, value - big_coins)

0 commit comments

Comments
 (0)