|
1 | | -\setcounter{tocdepth}{2}\tableofcontents |
2 | | -\newpage |
3 | | - |
4 | 1 | --- |
5 | 2 | title: "Playful Python: Learning the language through games and puzzles" |
6 | 3 | author: Ken Youens-Clark |
7 | 4 | ... |
8 | 5 |
|
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +\newpage |
| 10 | + |
| 11 | +\setcounter{tocdepth}{2}\tableofcontents |
| 12 | + |
9 | 13 | # Introduction |
10 | 14 |
|
11 | 15 | > "The only way to learn a new programming language is by writing programs in it." - Dennis Ritchie |
@@ -46,7 +50,7 @@ $ echo $PATH |
46 | 50 |
|
47 | 51 | Probably each directory is separated by a colon (`:`). *The order of the directories matters!* For instance, it's common to have more than one version of Python installed. When you type `python` on the command line, the directories in your `$PATH` are searched in order, and the first `python` found is the one that is used (and it's probably Python version 2!) |
48 | 52 |
|
49 | | -You could execute `new.py` by giving the full path to the program, e.g., `$HOME/work/playful_python/bin/new.py`, but that's really tedious. It's best to put `new.py` into one of the directories that is already in your `$PATH` like maybe `/usr/local/bin`. The problem is that you probably need administrator privileges to write to most of the directories that are in your `$PATH.`. If you are working on your laptop, this is probably not a problem, but if you are on a shared system, you probably won't be able to copy the program into your `$PATH` directories. |
| 53 | +You could execute `new.py` by giving the full path to the program, e.g., `$HOME/work/playful_python/bin/new.py`, but that's really tedious. It's best to put `new.py` into one of the directories that is already in your `$PATH` like maybe `/usr/local/bin`. The problem is that you probably need administrator privileges to write to most of the directories that are in your `$PATH`. If you are working on your laptop, this is probably not a problem, but if you are on a shared system, you probably won't be able to copy the program into your `$PATH` directories. |
50 | 54 |
|
51 | 55 | An alternative is to alter your `$PATH` to include the directory where `new.py` is located. E.g., if `new.py` is in `$HOME/work/playful_python/bin/`, then add this directory to your `$PATH` -- probably by editing `.bashrc` or `.bash_profile` located in your `$HOME` directory (if you use `bash`). See the documentation for your shell of choice to understand how to edit and persist your `$PATH`. |
52 | 56 |
|
@@ -1126,9 +1130,9 @@ But I find that fairly hard to read. |
1126 | 1130 |
|
1127 | 1131 | # Chapter 6: Telephone |
1128 | 1132 |
|
1129 | | -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. |
| 1133 | +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. |
1130 | 1134 |
|
1131 | | -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. |
| 1135 | +Each mutation to the text should be chosen using the `random` module, so your program will also need to accept a `-s|--seed` option (default `None`) to pass to the `random.seed` function for testing purposes. Print the resulting text after making the appropriate number of mutations. |
1132 | 1136 |
|
1133 | 1137 | ```` |
1134 | 1138 | $ ./telephone.py |
@@ -1166,39 +1170,27 @@ $ ./telephone.py -s 1 -m .5 ../inputs/fox.txt |
1166 | 1170 | Thakqkrck&brow- fo[ jumps#oWe,*L/C lxdy dogos |
1167 | 1171 | ```` |
1168 | 1172 |
|
1169 | | -## Mutations in DNA |
1170 | | - |
1171 | | -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. |
1172 | | - |
1173 | | -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! |
1174 | | - |
1175 | | -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. |
1176 | | - |
1177 | | -## Hints |
1178 | | - |
1179 | | -To create a combined error/usage statement for the `--mutations` error, look at `parser.error` in `argparse`. |
1180 | | - |
1181 | | -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": |
| 1173 | +Hints: |
1182 | 1174 |
|
| 1175 | +* To create a combined error/usage statement for the `--mutations` error, look at `parser.error` in `argparse`. |
| 1176 | +* 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": |
1183 | 1177 | ```` |
1184 | 1178 | >>> s = 'candle' |
1185 | 1179 | >>> s[0] = 'h' |
1186 | 1180 | Traceback (most recent call last): |
1187 | 1181 | File "<stdin>", line 1, in <module> |
1188 | 1182 | TypeError: 'str' object does not support item assignment |
1189 | 1183 | ```` |
1190 | | - |
1191 | | -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? |
1192 | | - |
1193 | | -For the replacement value, you should use `random.choice` from the union of the `string` class's `ascii_letters` and `punctuation`: |
1194 | | - |
| 1184 | +* 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? |
| 1185 | +* For the replacement value, you should use `random.choice` from the union of the `string` class's `ascii_letters` and `punctuation`: |
1195 | 1186 | ```` |
1196 | 1187 | >>> import string |
1197 | 1188 | >>> string.ascii_letters |
1198 | 1189 | 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' |
1199 | 1190 | >>> string.punctuation |
1200 | 1191 | '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' |
1201 | 1192 | ```` |
| 1193 | + |
1202 | 1194 | \newpage |
1203 | 1195 |
|
1204 | 1196 | ## Solution |
@@ -1275,6 +1267,96 @@ For the replacement value, you should use `random.choice` from the union of the |
1275 | 1267 | 69 main() |
1276 | 1268 | ```` |
1277 | 1269 |
|
| 1270 | +\newpage |
| 1271 | + |
| 1272 | +## Discussion |
| 1273 | + |
| 1274 | +The number of mutations will be proportional to the length of the text |
| 1275 | + |
| 1276 | +```` |
| 1277 | +>>> text = 'The quick brown fox jumps over the lazy dog.' |
| 1278 | +>>> len_text = len(text) |
| 1279 | +>>> len_text |
| 1280 | +44 |
| 1281 | +```` |
| 1282 | + |
| 1283 | +Since we chose the `--mutations` to be a `float` between 0 and 1, we can multiply that by the length to get the number of mutations to introduce. Since that number will likely be another `float` and we can introduce a partial number of mutations, we can use `int` to truncate the number to an integer value. |
| 1284 | + |
| 1285 | +```` |
| 1286 | +>>> mutations = .1 |
| 1287 | +>>> int(mutations * len_text) |
| 1288 | +4 |
| 1289 | +```` |
| 1290 | + |
| 1291 | +So we can use that number in a `for` loop with `range(4)` to modify four characters. To choose a character in the text to modify, I suggested to use `random.choice`: |
| 1292 | + |
| 1293 | +```` |
| 1294 | +>>> import random |
| 1295 | +>>> random.choice(range(len_text)) |
| 1296 | +1 |
| 1297 | +>>> random.choice(range(len_text)) |
| 1298 | +22 |
| 1299 | +```` |
| 1300 | + |
| 1301 | +If you assign that to a value like `i` (for "integer" and/or "index", it's pretty common to use `i` for this kind of value), then you could get the character at that position: |
| 1302 | + |
| 1303 | +```` |
| 1304 | +>>> i = random.choice(range(len_text)) |
| 1305 | +>>> i |
| 1306 | +4 |
| 1307 | +>>> text[i] |
| 1308 | +'q' |
| 1309 | +```` |
| 1310 | + |
| 1311 | +Now we saw earlier that we can't just change the `text`: |
| 1312 | + |
| 1313 | +```` |
| 1314 | +>>> text[i] = 'x' |
| 1315 | +Traceback (most recent call last): |
| 1316 | + File "<stdin>", line 1, in <module> |
| 1317 | +TypeError: 'str' object does not support item assignment |
| 1318 | +```` |
| 1319 | + |
| 1320 | +So we're going to have to create a *new* string using the text before and after `i` which we can get with string slices using `text[start:stop]`. If you leave out "start", Python starts at `0` (the beginning of the string), and if you leave out "stop" then it goes to the end, so `text[:]` is a copy of the entire string. |
| 1321 | + |
| 1322 | +The bit before `i` is: |
| 1323 | + |
| 1324 | +```` |
| 1325 | +>>> text[:i] |
| 1326 | +'The ' |
| 1327 | +```` |
| 1328 | + |
| 1329 | +And after `i` (skipping `i` itself, of course): |
| 1330 | + |
| 1331 | +```` |
| 1332 | +>>> text[i+1:] |
| 1333 | +'uick brown fox jumps over the lazy dog.' |
| 1334 | +```` |
| 1335 | + |
| 1336 | +There are many ways to join strings together into new strings, and the `+` operator is perhaps the simplest. So now we need some new character to insert in the middle which we can get with `random.choice` again, this time choosing from all the letters of the alphabet plus punctuation: |
| 1337 | + |
| 1338 | +```` |
| 1339 | +>>> import string |
| 1340 | +>>> alpha = string.ascii_letters + string.punctuation |
| 1341 | +>>> random.choice(alpha) |
| 1342 | +'n' |
| 1343 | +```` |
| 1344 | + |
| 1345 | +So to put it together, we overwrite the existing `text` so as to accumulate the changes over the iterations: |
| 1346 | + |
| 1347 | +```` |
| 1348 | +>>> text = text[:i] + random.choice(alpha) + text[i+1:] |
| 1349 | +>>> text |
| 1350 | +'The vuick brown fox jumps over the lazy dog.' |
| 1351 | +```` |
| 1352 | + |
| 1353 | +## Mutations in DNA |
| 1354 | + |
| 1355 | +For what it's worth, this is (sort of) how DNA changes over time. The machinery to copy DNA makes mistakes, and mutations randomly occur. Many times the change has no deleterious affect on the organism. Our example only changes characters to other characters, what are called "point mutations" or "single nucleotide variations" (SNV) or "single nucleotide polymorphisms" (SNP) in biology, but we could write a version that would also randomly delete or insert new characters which are called them "in-dels" (insertion-deletions) in biology. |
| 1356 | + |
| 1357 | +Mutations (that don't result in the demise of the organism) occur at a fairly standard rate, so counting the number of mutations between a conserved region of any two organisms can allow an estimate of how long ago they diverged from a common ancestor! 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. |
| 1358 | + |
| 1359 | + |
1278 | 1360 | \newpage |
1279 | 1361 |
|
1280 | 1362 | # Chapter 7: Bottles of Beer Song |
|
0 commit comments