You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -412,13 +412,16 @@ This is the fun part. Once you have a mapping of word pairs to following words,
412
412
# pick a random item from a sequence
413
413
random.choice(a_list)
414
414
415
-
This is all pretty tricky to test -- after all, you are selecting random words -- you can't know what the result should be! There are two tactics you can take here. We show a bit of both in the provided tests.
415
+
This is all pretty tricky to test -- after all, you are selecting random words -- you can't know what the result should be! There are two tactics you can take here.
416
416
417
417
Tactic one is to break you code down into pieces that you *can*test -- everything BUT the random choices.
418
418
419
-
Tactic two is to take advantage of the random "`seed <https://en.wikipedia.org/wiki/Random_seed>`_". Computers don't really make truly random numbers. What they do is compute a sequence of numbers that are statistically very much like random numbers. But if you start with the same initial value, known as the "seed", then you will get the same sequence of numbers. We can take advantage of this in our tests, as the built in ``random`` module provides a way to `set the seed <https://docs.python.org/3/library/random.html#random.seed>`_
419
+
Tactic two is to set the random seed before each test, to assure the same result.
420
+
The built in``random`` module `provides a way to set the seed <https://docs.python.org/3/library/random.html#random.seed>`_: the ``random.seed()`` function.
420
421
421
-
The provided tests use a both of these tactics.
422
+
.. note:: Computers don't really make truly random numbers. What they do is compute a sequence of numbers that are statistically very much like random numbers, known as `"pseudo random numbers" <https://en.wikipedia.org/wiki/Pseudorandom_number_generator>`_. If you start with the same initial value, known as the "seed", then you will get the same sequence of numbers. `Random seed <https://en.wikipedia.org/wiki/Random_seed>`_
423
+
424
+
The provided tests use both of these tactics.
422
425
423
426
- You need to start with the first word pair; picking a random key from a dict is actually a bit tricky. But we have a test for it:
424
427
@@ -434,35 +437,116 @@ The provided tests use a both of these tactics.
434
437
# set the seed so we'll always get the same one
435
438
random.seed(1234)
436
439
pair = trigrams.pick_random_pair(test_pairs)
437
-
440
+
print("the pair is:", pair)
438
441
assert pair == ('six', 'seven')
439
442
440
443
So you'll need to define a function: ``pick_random_pair()`` that takes your trigram dict as input, and returns a random key.
441
444
442
-
Note that that particular result is using a particular algorithm -- if you use a different one, you might get a different pair -- but you should get the same one every time, so you can make the test check for that.
445
+
Note that the particular result in the test is using a particular algorithm -- if you use a different one, you might get a different pair -- but you should get the same one every time, so you can make the test check for that.
446
+
447
+
Once you've got the first starting pair, you'll need to make your text,
448
+
so you'll need a data structure to build it up in. You probably want to build it up in a list, appending one word at a time. You can join it together at the end with ``"".join(the_list_of_words)``, which will make a string, separating the words with a space.
449
+
450
+
Remember that after adding a word to a pair to make a three-word text, the next pair is the last two words in that three-word text.
write a function: ``get_last_pair()`` that takes a list of words, and returns the last two words as a tuple.
462
+
463
+
Then you'll need to pick a random word from the "followers" -- the words that followed that pair of words in the original text. There is a test for that, too:
word = trigrams.get_random_follower(tri_dict, ("one", "two"))
477
+
print("got word:", word)
478
+
assert word == "seven"
479
+
480
+
Again, this sets the random seed so that you will always get the same answer. If your code returns a different word -- change the test to match.
481
+
482
+
But what if the word pair is not in the dict? It's not that likely in a long text, but it can happen. So make sure that your code handles that situation by making sure it passes this test:
word = trigrams.get_random_follower(tri_dict, ("one", "one"))
496
+
print("got word:", word)
497
+
assert word # this asserts that you got a non-empty string
443
498
499
+
Note that there are a number of options here as to what to do -- but make sure it returns *something*.
444
500
445
-
- As you build up your text, you probably want to build it up in a list, appending one word at a time. You can join it together at the end with ``" ".join(the_list_of_words)``
501
+
Putting it Together
502
+
...................
446
503
447
-
- Remember that after adding a word to a pair to make a three-word text, the next pair is the last two words in that three-word text.
504
+
You now have the pieces you need to make some new text. Let's write a function that will make a single sentence a specified number of words long. The first word should be capitalized, and it should end with a period. Here is the test for that function:
448
505
449
-
- What to do if you end up with a word pair that isn't in the original text? It's unlikely on a long text, but possible.
506
+
.. code-block:: python
507
+
508
+
def test_make_sentence():
509
+
"""
510
+
test making a trigrams sentence
511
+
512
+
as it is supposed to be random, this tests for things other than
513
+
the actual results.
514
+
515
+
NOTE that this test relies on the build_trigram() function, so it
516
+
will fail if that doesn't work.
517
+
"""
518
+
# use the already tested build_trigram function to make the dict
519
+
tri_dict = trigrams.build_trigram(LONGER_TEXT)
450
520
451
-
- How to terminate? Probably have a pre-defined length of text!
452
521
522
+
# make a sentence of 6 words
523
+
sentence = trigrams.make_sentence(tri_dict, 6)
453
524
525
+
print(sentence)
526
+
# check that it has 6 words
527
+
assert len(sentence.split()) == 6
528
+
# check that the first letter is a capital
529
+
assert sentence[0] == sentence[0].upper()
530
+
# check that it ends with a period
531
+
assert sentence[-1] == "."
532
+
# check that there is not a space between the period and the last word.
533
+
assert not sentence[-2].isspace()
454
534
455
-
Once you have the basics working, try your code on a longer piece of input text. Then think about making it fancy. Can you make sentences with capitalized first words and punctuation? Anything else to make the text more "real"?
535
+
You can now use the previous functions to make a ``make_sentence()`` function that passes these tests.
536
+
537
+
538
+
Once you have the basics working, try your code on a longer piece of input text. Then think about making it fancy: put a number of sentences of random length to form a paragraph? Add in some other random punctuation? Anything else to make the text more "real"?
456
539
457
540
458
541
Processing the Input Text
459
542
-------------------------
460
543
461
544
If you get a book from Project Gutenberg (or anywhere else), it will not be "clean." That is, it will have header information, footer information, chapter headings, punctuation, what have you. So you'll need to clean it up somehow to get a simple list of words to use to build your trigrams.
462
545
463
-
The first part of the process is pretty straightforward; open the file and loop through the lines of text.
546
+
The first part of the process is pretty straightforward; open the file and loop through the lines of text and process them.
464
547
465
548
You may want to skip the header. How would you do that??
549
+
466
550
Hint: in a Project Gutenberg e-book, there is a line of text that starts with::
467
551
468
552
*** START OF THIS PROJECT GUTENBERG EBOOK
@@ -474,27 +558,36 @@ In the loop, you can process a single line of text to break it into words:
474
558
Optional steps to cleaning up the text:
475
559
476
560
- Strip out punctuation?
477
-
- If you do this, what about contractions, i.e. the appostrophein"can't" vs. a single quotation mark -- which are the same character.
561
+
- If you do this, what about contractions, i.e. the apostrophe in "can't" vs. a single quotation mark -- which are the same character.
478
562
479
563
- Remove capitalization?
480
564
- If you do this, what about "I"? And proper nouns?
481
565
482
566
Any other ideas you may have.
483
567
568
+
Be sure to use TDD as you develop the "clean up" code: write a test for one feature, and then make sure your code passes that test.
569
+
570
+
Lather, rinse and repeat.
571
+
484
572
**Hints:**
485
573
486
574
The ``string`` methods are your friend here.
487
575
488
576
There are also handy constants in the ``string`` module: ``import string``
577
+
(https://docs.python.org/3/library/string.html)
489
578
490
579
Check out the ``str.translate()`` method; it can make multiple replacements very fast.
491
580
492
581
Do get the full trigrams code working first, then play with some of the fancier options.
493
582
583
+
494
584
Code Structure
495
585
--------------
496
586
497
-
Break your code down into a handful of separate functions. This way you can test each on its own, and it's easier to refactor one part without messing with the others. For instance, your ``__main__`` block might look something like:
587
+
You will have found that following TDD forces you to break your code down into a handful of separate functions, each of which does only one thing. This lets you test each function on its own, and it's easier to refactor one part without messing with the others. Then you can put them all together into a simple program.
588
+
589
+
590
+
For instance, your ``__main__`` block might look something like:
498
591
499
592
.. code-block:: python
500
593
@@ -512,3 +605,5 @@ Break your code down into a handful of separate functions. This way you can test
0 commit comments