X Tutup
Skip to content

Commit a19e5cf

Browse files
committed
telephone
1 parent 9daecc2 commit a19e5cf

File tree

7 files changed

+418
-35
lines changed

7 files changed

+418
-35
lines changed

bin/chapters.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ jump_the_five
33
picnic
44
howler
55
apples_and_bananas
6+
telephone
67
bottles_of_beer
78
gashlycrumb
89
movie_reader

book.md

Lines changed: 188 additions & 35 deletions
Large diffs are not rendered by default.

playful_python.pdf

7 KB
Binary file not shown.

telephone/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.PHONY: test
2+
3+
test:
4+
pytest -xv test.py

telephone/README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Telephone
2+
3+
Perhaps you remember the game of "Telephone" where a message is secretly passed through a series of intermediaries and then the result at the end of the chain is compared with how it started? This is like that, only we're going to take some`text` (from the command line or a file) and mutate it by some percentage `-m|--mutations` (a number between 0 and 1, default `0.1` or 10%) and then print out the resulting text.
4+
5+
Each mutation to the text should be chosen using the `random` module, so your program will also need to accept a `-s|--seed` option to pass to the `random.seed` function for testing purposes. Print the resulting text after making the appropriate number of mutations.
6+
7+
````
8+
$ ./telephone.py
9+
usage: telephone.py [-h] [-s str] [-m float] str
10+
telephone.py: error: the following arguments are required: str
11+
$ ./telephone.py -h
12+
usage: telephone.py [-h] [-s str] [-m float] str
13+
14+
Telephone
15+
16+
positional arguments:
17+
str Input text or file
18+
19+
optional arguments:
20+
-h, --help show this help message and exit
21+
-s str, --seed str Random seed (default: None)
22+
-m float, --mutations float
23+
Percent mutations (default: 0.1)
24+
````
25+
26+
The program should not accept a bad `--mutations` argument:
27+
28+
````
29+
$ ./telephone.py -m 10 foo
30+
usage: telephone.py [-h] [-s str] [-m float] str
31+
telephone.py: error: --mutations "10.0" must be b/w 0 and 1
32+
````
33+
34+
It can be interesting to watch the accumulation of mutations:
35+
36+
````
37+
$ ./telephone.py -s 1 ../inputs/fox.txt
38+
Tho quick brown foa jumps oWer*the lazy dog.
39+
$ ./telephone.py -s 1 -m .5 ../inputs/fox.txt
40+
Thakqkrck&brow- fo[ jumps#oWe,*L/C lxdy dogos
41+
````
42+
43+
## Mutations in DNA
44+
45+
For what it's worth, this is how DNA changes over time. The machinery to copy DNA makes mistakes, and mutations randomly occur. Many times the change is in a part of the DNA that doesn't affect the organism or is a "synonymous" change that doesn't end up affecting the function of the DNA. Our example will only change characters to other characters, what are called "point mutations" or "single nucleotide variations" (SNV) or "single nucleotide polymorphisms" (SNP) in biology. We could write a version that would also randomly delete or insert new characters which are called them "in-dels" (insertion-deletions) in biology.
46+
47+
Mutations (that don't result in the demise of the organism) occur at a fairly standard rate, so counting the number of point mutations (AKA ) between a conserved region of any two organisms, we can estimate how long ago they diverged from a common ancestor!
48+
49+
We can revisit the output of this program later by using the Hamming distance to find how many changes we'd need to make to the output to regain the input.
50+
51+
## Hints
52+
53+
To create a combined error/usage statement for the `--mutations` error, look at `parser.error` in `argparse`.
54+
55+
To select a character position to change, I suggest using `random.choice` and a `range` from length of the incoming text. With that, you'll need to alter the character at that position, but you'll find that strings in Python are *immutable*. For instance, if I wanted to change "candle" into "handle":
56+
57+
````
58+
>>> s = 'candle'
59+
>>> s[0] = 'h'
60+
Traceback (most recent call last):
61+
File "<stdin>", line 1, in <module>
62+
TypeError: 'str' object does not support item assignment
63+
````
64+
65+
So, I need to create a *new string* that has `h` joined to the rest of the string `s` after the zeroth position. How could you do that?
66+
67+
For the replacement value, you should use `random.choice` from the union of the `string` class's `ascii_letters` and `punctuation`:
68+
69+
````
70+
>>> import string
71+
>>> string.ascii_letters
72+
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
73+
>>> string.punctuation
74+
'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
75+
````

telephone/solution.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env python3
2+
"""Telephone"""
3+
4+
import argparse
5+
import os
6+
import random
7+
import string
8+
import sys
9+
10+
11+
# --------------------------------------------------
12+
def get_args():
13+
"""Get command-line arguments"""
14+
15+
parser = argparse.ArgumentParser(
16+
description='Telephone',
17+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
18+
19+
parser.add_argument('input',
20+
metavar='str',
21+
help='Input text or file')
22+
23+
parser.add_argument('-s',
24+
'--seed',
25+
help='Random seed',
26+
metavar='str',
27+
type=str,
28+
default=None)
29+
30+
parser.add_argument('-m',
31+
'--mutations',
32+
help='Percent mutations',
33+
metavar='float',
34+
type=float,
35+
default=0.1)
36+
37+
args = parser.parse_args()
38+
39+
if not 0 < args.mutations <= 1:
40+
msg = '--mutations "{}" must be b/w 0 and 1'.format(args.mutations)
41+
parser.error(msg)
42+
43+
return args
44+
45+
46+
# --------------------------------------------------
47+
def main():
48+
"""Make a jazz noise here"""
49+
50+
args = get_args()
51+
text = args.input
52+
random.seed(args.seed)
53+
54+
if os.path.isfile(text):
55+
text = open(text).read()
56+
57+
len_text = len(text)
58+
num_mutations = int(args.mutations * len_text)
59+
alpha = string.ascii_letters + string.punctuation
60+
61+
for _ in range(num_mutations):
62+
i = random.choice(range(len_text))
63+
text = text[:i] + random.choice(alpha) + text[i+1:]
64+
65+
print(text.rstrip())
66+
67+
# --------------------------------------------------
68+
if __name__ == '__main__':
69+
main()

telephone/test.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env python3
2+
"""tests for telephone.py"""
3+
4+
from subprocess import getstatusoutput, getoutput
5+
import os
6+
import re
7+
8+
prg = "./telephone.py"
9+
fox = '../inputs/fox.txt'
10+
now = '../inputs/now.txt'
11+
12+
13+
# --------------------------------------------------
14+
def test_exists():
15+
"""exists"""
16+
17+
assert os.path.isfile(prg)
18+
19+
20+
# --------------------------------------------------
21+
def test_usage():
22+
"""usage"""
23+
24+
for flag in ['', '-h', '--help']:
25+
out = getoutput('{} {}'.format(prg, flag))
26+
assert re.match('usage', out, re.IGNORECASE)
27+
28+
29+
# --------------------------------------------------
30+
def test_bad_mutation():
31+
"""bad mutation values"""
32+
33+
for val in ['-1.0', '10.0']:
34+
rv, out = getstatusoutput('{} -m {} {}'.format(prg, val, fox))
35+
assert rv > 0
36+
assert re.search('--mutations "{}" must be b/w 0 and 1'.format(val),
37+
out)
38+
39+
40+
# --------------------------------------------------
41+
def test_text01():
42+
"""test"""
43+
44+
txt = open(now).read().rstrip()
45+
rv, out = getstatusoutput('{} -s 1 "{}"'.format(prg, txt))
46+
assert rv == 0
47+
expected = """
48+
Now ao the time for all good-men to came to the aid of Whe pa*ty
49+
""".strip()
50+
assert out.rstrip() == expected
51+
52+
53+
# --------------------------------------------------
54+
def test_text02():
55+
"""test"""
56+
57+
txt = open(now).read().rstrip()
58+
rv, out = getstatusoutput('{} -s 2 -m .4 "{}"'.format(prg, txt))
59+
assert rv == 0
60+
expected = """
61+
NUw iVKPqe time fErXal; gPod"'en to come`Do *Dl aFd of!the Yabta
62+
""".strip()
63+
assert out.rstrip() == expected
64+
65+
66+
# --------------------------------------------------
67+
def test_file01():
68+
"""test"""
69+
70+
rv, out = getstatusoutput('{} --seed 1 {}'.format(prg, fox))
71+
assert rv == 0
72+
assert out.rstrip() == 'Tho quick brown foa jumps oWer*the lazy dog.'
73+
74+
75+
# --------------------------------------------------
76+
def test_file02():
77+
"""test"""
78+
79+
rv, out = getstatusoutput('{} --seed 2 --mutations .6 {}'.format(prg, fox))
80+
assert rv == 0
81+
assert out.rstrip() == 'UheK+u*ckXbrPw~ fox Du*#FT{ver a~e}|Uzy (T?l'

0 commit comments

Comments
 (0)
X Tutup