Skip to content

Commit d98f891

Browse files
committed
changes
1 parent daa10c8 commit d98f891

File tree

5 files changed

+142
-39
lines changed

5 files changed

+142
-39
lines changed

bin/chapters.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ blackjack
1919
family_tree
2020
gematria
2121
histy
22+
pareto
2223
#guess
2324
mommys_little_helper
2425
kentucky_friar

bin/new.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,14 @@ def get_args():
132132
default=0)
133133
134134
parser.add_argument('-f',
135-
'--flag',
135+
'--file',
136+
help='A readable file',
137+
metavar='FILE',
138+
type=argparse.FileType('r'),
139+
default=0)
140+
141+
parser.add_argument('-o',
142+
'--on',
136143
help='A boolean flag',
137144
action='store_true')
138145

book.md

Lines changed: 102 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4936,7 +4936,93 @@ W 375 ###
49364936

49374937
\newpage
49384938

4939-
# Chapter 19: Mommy's Little (Crossword) Helper
4939+
# Chapter 19: Modeling the Pareto Principle
4940+
4941+
Model the 80/20 rule.
4942+
4943+
https://en.wikipedia.org/wiki/Pareto_principle
4944+
4945+
\newpage
4946+
4947+
## Solution
4948+
4949+
````
4950+
1 #!/usr/bin/env python3
4951+
2 """
4952+
3 Author : kyclark
4953+
4 Date : 2019-07-12
4954+
5 Purpose: Rock the Casbah
4955+
6 """
4956+
7
4957+
8 import argparse
4958+
9 import os
4959+
10 import random
4960+
11 import sys
4961+
12
4962+
13
4963+
14 # --------------------------------------------------
4964+
15 def get_args():
4965+
16 """Get command-line arguments"""
4966+
17
4967+
18 parser = argparse.ArgumentParser(
4968+
19 description='Argparse Python script',
4969+
20 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
4970+
21
4971+
22 parser.add_argument('-a',
4972+
23 '--actors',
4973+
24 help='Number of actors',
4974+
25 metavar='int',
4975+
26 type=int,
4976+
27 default=50)
4977+
28
4978+
29 parser.add_argument('-u',
4979+
30 '--units',
4980+
31 help='Number of units',
4981+
32 metavar='int',
4982+
33 type=int,
4983+
34 default=500)
4984+
35
4985+
36 parser.add_argument('-r',
4986+
37 '--rounds',
4987+
38 help='Number of rounds',
4988+
39 metavar='int',
4989+
40 type=int,
4990+
41 default=100)
4991+
42
4992+
43 return parser.parse_args()
4993+
44
4994+
45
4995+
46 # --------------------------------------------------
4996+
47 def main():
4997+
48 """Make a jazz noise here"""
4998+
49
4999+
50 args = get_args()
5000+
51 actors = list(range(1, args.actors + 1))
5001+
52 units = args.units
5002+
53 rounds = args.rounds
5003+
54 units_per_actor = int(units / len(actors))
5004+
55 dist = {actor: units_per_actor for actor in actors}
5005+
56
5006+
57 for i in range(rounds):
5007+
58 random.shuffle(actors)
5008+
59 for i in range(0, len(actors), 2):
5009+
60 a1, a2 = actors[i], actors[i+1]
5010+
61 if dist[a1] and dist[a2]:
5011+
62 res = random.choice([0,1])
5012+
63 dist[a1] += 1 if res else -1
5013+
64 dist[a2] += 1 if res else -1
5014+
65
5015+
66 for units, actor in sorted([(v,k) for k,v in dist.items()]):
5016+
67 print('{:3}: {:3} {}'.format(actor, units, '#' * units))
5017+
68
5018+
69 # --------------------------------------------------
5019+
70 if __name__ == '__main__':
5020+
71 main()
5021+
````
5022+
5023+
\newpage
5024+
5025+
# Chapter 20: Mommy's Little (Crossword) Helper
49405026

49415027
Write a Python program called `helper.py` that finds all words matching a given `-p|--pattern` such as one might use to complete a crossword puzzle to find words matching from a given `-w|--wordlist` (default `/usr/share/dict/words`). E.g., all 5-letter words with a "t" as the second character and ending in "ed". I could do this on the command line like so:
49425028

@@ -5188,7 +5274,7 @@ If both conditions are `True` (same length, all characters the same), then I `ap
51885274
All that is left is to check if any words matched. If so, we print them out, numbered and nicely aligned; otherwise, we let the user know that no matches were found. I hope you tried solving this problem with and without regular expressions as there is much to learn by each method.
51895275
\newpage
51905276

5191-
# Chapter 20: Kentucky Friar
5277+
# Chapter 21: Kentucky Friar
51925278

51935279
Write a Python program called `friar.py` that reads some input text from a single positional argument on the command line (which could be a file to read) and transforms the text by dropping the "g" from words two-syllable words ending in "-ing" and also changes "you" to "y'all". Be mindful to keep the case the same on the first letter, e.g, "You" should become "Y'all," "Hunting" should become "Huntin'".
51945280

@@ -5368,7 +5454,7 @@ Finally we need to apply our `fry` function to all the pieces we got from splitt
53685454
````
53695455
\newpage
53705456

5371-
# Chapter 21: Mad Libs
5457+
# Chapter 22: Mad Libs
53725458

53735459
![This definitely not a copyright infringment.](images/mad_libs.png)
53745460

@@ -5680,7 +5766,7 @@ The `count=1` is necessary to prevent `re.sub` from replacing *every* instance o
56805766

56815767
\newpage
56825768

5683-
# Chapter 22: License Plates
5769+
# Chapter 23: License Plates
56845770

56855771
Write a Python program called `license.py` that will create a regular expression for a license plate that accounts for characters and numbers which might be confused according to the following list:
56865772

@@ -5813,7 +5899,7 @@ In creating all the possible plates from your regular expression, you are making
58135899

58145900
\newpage
58155901

5816-
# Chapter 23: Gibberish Generator
5902+
# Chapter 24: Gibberish Generator
58175903

58185904
Write a Python program called `gibberish.py` that uses the Markov chain algorithm to generate new words from the words in a set of training files. The program should take one or more positional arguments which are files that you read, word-by-word, and note the options of letters after a given `-k|--kmer_size` (default `2`) grouping of letters. E.g., in the word "alabama" with `k=1`, the frequency table will look like:
58195905

@@ -6284,7 +6370,7 @@ Now you can talk the "Markov Chain" problem that moves to the level of words and
62846370

62856371
\newpage
62866372

6287-
# Chapter 24: Piggy (Pig Latin)
6373+
# Chapter 25: Piggy (Pig Latin)
62886374

62896375
Write a Python program named `piggy.py` that takes one or more file names as positional arguments and converts all the words in them into "Pig Latin" (see rules below). Write the output to a directory given with the flags `-o|--outdir` (default `out-yay`) using the same basename as the input file, e.g., `input/foo.txt` would be written to `out-yay/foo.txt`.
62906376

@@ -6694,7 +6780,7 @@ That is the crux of the program. All that is left is to report to the user how m
66946780

66956781
\newpage
66966782

6697-
# Chapter 25: Soundex Rhymer
6783+
# Chapter 26: Soundex Rhymer
66986784

66996785
Write a Python program called `rhymer.py` that uses the Soundex algorithm/module to find words that rhyme with a given input word. When comparing words, you sometimes want to discount any leading consonants, e.g., the words "listen" and "glisten" rhyme but only if you compare the "isten" part, so the program should have an optional flag `-s|--stem` to indicate that the given word and the words you compare should both be trimmed to the "stem". The program should take an optional `-w|--wordlist` argument (default `/usr/share/dict/words`) for the comparisons and should respond, as always, to `-h|--help` for usage.
67006786

@@ -7013,7 +7099,7 @@ Once I have the `stemmer` function, I can apply it to the given `word` and every
70137099

70147100
\newpage
70157101

7016-
# Chapter 26: Anagram
7102+
# Chapter 27: Anagram
70177103

70187104
Write a program called `presto.py` that will find anagrams of a given positional argument. The program should take an optional `-w|--wordlist` (default `/usr/share/dict/words`) and produce output that includes combinations of `-n|num_combos` words (default `1`) that are anagrams of the given input.
70197105

@@ -7463,7 +7549,7 @@ In the end, I look to see how many `anagrams` I found using `len(anagrams)`. If
74637549

74647550
\newpage
74657551

7466-
# Chapter 27: Hangman
7552+
# Chapter 28: Hangman
74677553

74687554
Write a Python program called `hangman.py` that will play a game of Hangman which is a bit like "Wheel of Fortune" where you present the user with a number of elements indicating the length of a word. For our game, use the underscore `_` to indicate a letter that has not been guessed. The program should take `-n|--minlen` minimum length (default `5`) and `-l|--maxlen` maximum length options (default `10`) to indicate the minimum and maximum lengths of the randomly chosen word taken from the `-w|--wordlist` option (default `/usr/share/dict/words`). It also needs to take `-s|--seed` to for the random seed and the `-m|--misses` number of misses to allow the player.
74697555

@@ -8033,7 +8119,7 @@ Here are some changes you could make to your program:
80338119
* Add a `quiet` flag to keep `play` from executing any `print` statements
80348120
\newpage
80358121

8036-
# Chapter 28: First Bank of Change
8122+
# Chapter 29: First Bank of Change
80378123

80388124
Write a Python program called `fboc.py` that will figure out all the different combinations of pennies, nickels, dimes, and quarters in a given `value` provided as a single positional argument. The value must be greater than 0 and less than or equal to 100. It should provide a usage if given no arguments or the `-h|--help` flag:
80398125

@@ -8275,7 +8361,7 @@ The `plural` version of each name is made by adding `s` except for `penny`, so l
82758361
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.
82768362
\newpage
82778363

8278-
# Chapter 29: Runny Babbit
8364+
# Chapter 30: Runny Babbit
82798365

82808366
Are you familiar with Spoonerisms where the initial consonant sounds of two words are switched? According to Wikipedia, they get their name from William Archibald Spooner who did this often. The author Shel Silverstein wrote a wonderful book called _Runny Babbit_ ("bunny rabbit") based on this. So, let's write a Python program called `runny_babbit.py` that will read some text or an input file given as a single positional argument and finds neighboring words with initial consonant sounds to swap. As we'll need to look at pairs of words and in such as way that it will make it difficult to remember the original formatting of the text, let's also take a `-w|--width` (default `70`) to format the output text to a maximum width.
82818367

@@ -8559,7 +8645,7 @@ The runny babbit is cute.
85598645
````
85608646
\newpage
85618647

8562-
# Chapter 30: Markov Chain
8648+
# Chapter 31: Markov Chain
85638649

85648650
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:
85658651

@@ -8820,7 +8906,7 @@ But there will be spaces in between each word, so I account for them by adding o
88208906
At this point, the `words` list needs to be turned into text. It would be ugly to just `print` out one long string, so I use the `textwrap.wrap` to break up the long string into lines that are no longer than the given `text_width`. That function returns a list of lines that need to be joined on newlines to print.
88218907
\newpage
88228908

8823-
# Chapter 31: Hamming Chain
8909+
# Chapter 32: Hamming Chain
88248910

88258911
Write a Python program called `chain.py` that takes a `-s|--start` word and searches a `-w|--wordlist` argument (default `/usr/local/share/dict`) for words no more than `-d|--max_distance` Hamming distance for some number of `-i|--iteration` (default `20`). Be sure to accept a `-S|--seed` for `random.seed`.
88268912

@@ -9023,7 +9109,7 @@ Failed to find more words!
90239109

90249110
\newpage
90259111

9026-
# Chapter 32: Morse Encoder/Decoder
9112+
# Chapter 33: Morse Encoder/Decoder
90279113

90289114
Write a Python program called `morse.py` that will encrypt/decrypt text to/from Morse code. The program should expect a single positional argument which is either the name of a file to read for the input or the character `-` to indicate reading from STDIN. The program should also take a `-c|--coding` option to indicate use of the `itu` or standard `morse` tables, `-o|--outfile` for writing the output (default STDOUT), and a `-d|--decode` flag to indicate that the action is to decode the input (the default is to encode it).
90299115

@@ -9241,7 +9327,7 @@ THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.
92419327

92429328
\newpage
92439329

9244-
# Chapter 33: ROT13 (Rotate 13)
9330+
# Chapter 34: ROT13 (Rotate 13)
92459331

92469332
Write a Python program called `rot13.py` that will encrypt/decrypt input text by shifting the text by a given `-s|--shift` argument or will move each character halfway through the alphabet, e.g., "a" becomes "n," "b" becomes "o," etc. The text to rotate should be provided as a single positional argument to your program and can either be a text file, text on the command line, or `-` to indicate STDIN so that you can round-trip data through your program to ensure you are encrypting and decrypting properly.
92479333

@@ -9406,7 +9492,7 @@ The quick brown fox jumps over the lazy dog.
94069492

94079493
\newpage
94089494

9409-
# Chapter 34: Word Search
9495+
# Chapter 35: Word Search
94109496

94119497
Write a Python program called `search.py` that takes a file name as the single positional argument and finds the words hidden in the puzzle grid.
94129498

pareto/solution.py

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ def get_args():
5050

5151
parser.add_argument('-g',
5252
'--graph',
53-
help='Show histograms',
54-
action='store_true')
53+
help='Plot histograms to file',
54+
metavar='FILE',
55+
type=str,
56+
default='')
5557

5658
return parser.parse_args()
5759

@@ -65,42 +67,48 @@ def main():
6567

6668
results = []
6769
for i in range(args.rounds):
68-
res = sim(num_actors=args.actors,
69-
num_units=args.units,
70-
distribution=args.distribution,
71-
graph=args.graph)
70+
res, dist = sim(num_actors=args.actors,
71+
num_units=args.units,
72+
distribution=args.distribution)
7273

73-
print('{:3}: {}'.format(i + 1, res))
74+
if args.graph:
75+
fh = open(args.graph + '-{}.txt'.format(i+1), 'wt')
76+
for units, actor in sorted([(v, k) for k, v in dist.items()]):
77+
fh.write('{:3}: {:3} {}\n'.format(actor, units, '#' * units))
78+
fh.close()
79+
80+
print('{:3}: {} iterations'.format(i + 1, res))
7481
results.append(res)
7582

76-
print('Average = {}'.format(sum(results) / len(results)))
83+
print('Average = {:,d} iterations'.format(int(sum(results) /
84+
len(results))))
7785

7886

7987
# --------------------------------------------------
80-
def sim(num_actors, num_units, distribution, graph=False):
88+
def sim(num_actors, num_units, distribution):
8189
"""Run a simulation"""
8290

8391
actors = list(range(1, num_actors + 1))
8492
units_per_actor = int(num_units / num_actors)
93+
assert units_per_actor > 0, 'Not enough units per actor'
8594
dist = {actor: units_per_actor for actor in actors}
95+
rounds = 0
8696

8797
while True:
98+
rounds += 1
8899
random.shuffle(actors)
89100
for i in range(0, len(actors), 2):
90101
a1, a2 = actors[i], actors[i + 1]
91-
if dist[a1] and dist[a2]:
92-
res = random.choice([0, 1])
93-
dist[a1] += 1 if res else -1
94-
dist[a2] += 1 if res else -1
95-
96-
if get_dist(dist, percentile=distribution) <= 1 - distribution:
97-
print('Reached {} in {}'.format(distribution, i + 1))
98-
if graph:
99-
for units, actor in sorted([(v, k)
100-
for k, v in dist.items()]):
101-
print('{:3}: {:3} {}'.format(actor, units,
102-
'#' * units))
103-
return i + 1
102+
if all([dist[a1], dist[a2]]):
103+
if random.choice([True, False]):
104+
dist[a1] += 1
105+
dist[a2] -= 1
106+
else:
107+
dist[a1] -= 1
108+
dist[a2] += 1
109+
110+
if get_dist(dist, percentile=distribution) <= 1 - distribution:
111+
return rounds, dist
104112

105113
return 0
106114

@@ -114,6 +122,7 @@ def get_dist(dist, percentile=.8):
114122

115123
values = sorted(list(dist.values()), reverse=True)
116124
total = sum(values)
125+
assert total > 0
117126
num_actors = len(values)
118127
for i in range(1, num_actors + 1):
119128
cum_sum = sum(values[:i])

playful_python.pdf

2.51 KB
Binary file not shown.

0 commit comments

Comments
 (0)