Initial commit
This commit is contained in:
commit
370415e29b
|
@ -0,0 +1,28 @@
|
|||
# Types Lab
|
||||
|
||||
In this lab, we learn about types by writing functions whose inputs and outputs
|
||||
are English parts of speech. Once you finish, you will be able to generate
|
||||
syntactically-correct phrases which you can use to write auto-poetry like:
|
||||
|
||||
Roses are red<br>
|
||||
Violets are blue<br>
|
||||
Evenings are poisonous<br>
|
||||
And so are you.<br>
|
||||
|
||||
I used to have a formative scratch<br>
|
||||
Until i swapped it for an icy latch.
|
||||
|
||||
There once was a person named pete<br>
|
||||
Who owned an optical cleat.<br>
|
||||
He widely canvassed it,<br>
|
||||
And then he canvased it,<br>
|
||||
Until it turned into a beat.
|
||||
|
||||
## Usage
|
||||
|
||||
- `vocabulary.py` has a bunch of functions for picking random words.
|
||||
` `grammatical_types.py` defines types like `Noun`, `Adjective`, and `TransitiveVerb`.
|
||||
- `grammar.py` has a bunch of functions for combining and transforming
|
||||
grammatical types. These are unfinished; it's your job to write them.
|
||||
- `poetry.py` has functions for writing beautiful auto-poems. Once `grammar.py`
|
||||
is complete, try running `python poetry.py`.
|
|
@ -0,0 +1,79 @@
|
|||
from vocabulary import starts_with_vowel_sound
|
||||
from grammatical_types import (
|
||||
Adjective,
|
||||
Adverb,
|
||||
DeterminedNounPhrase,
|
||||
Determiner,
|
||||
IntransitiveVerb,
|
||||
Noun,
|
||||
NounPhrase,
|
||||
PastTenseTransitiveVerb,
|
||||
PastTenseIntransitiveVerb,
|
||||
PluralNoun,
|
||||
Sentence,
|
||||
TransitiveVerb,
|
||||
VerbPhrase,
|
||||
)
|
||||
|
||||
# 3A: LOVE POEM
|
||||
|
||||
def pluralize(noun):
|
||||
"Noun -> PluralNoun"
|
||||
assert type(noun) == Noun
|
||||
if noun.endswith("s") or noun.endswith("ch") or noun.endswith("sh") or noun.endswith("x"):
|
||||
return PluralNoun(noun + 'es')
|
||||
else:
|
||||
return PluralNoun(noun + 's')
|
||||
|
||||
# 3B: COUPLET
|
||||
|
||||
def noun_phrase(adjective, noun):
|
||||
"(Adjective, Noun) -> NounPhrase"
|
||||
#YOUR CODE GOES HERE
|
||||
|
||||
raise NotImplementedError("This function isn't done!")
|
||||
|
||||
def determine_noun_phrase(determiner, noun_phrase):
|
||||
"(Determiner, NounPhrase) -> Determined NounPhrase"
|
||||
#YOUR CODE GOES HERE
|
||||
|
||||
raise NotImplementedError("This function isn't done!")
|
||||
|
||||
def make_definite(noun_phrase):
|
||||
"NounPhrase -> DeterminedNounPhrase"
|
||||
#YOUR CODE GOES HERE
|
||||
|
||||
raise NotImplementedError("This function isn't done!")
|
||||
|
||||
def make_indefinite(noun_phrase):
|
||||
"NounPhrase -> DeterminedNounPhrase"
|
||||
#YOUR CODE GOES HERE
|
||||
|
||||
raise NotImplementedError("This function isn't done!")
|
||||
|
||||
# 3C: LIMERICK
|
||||
|
||||
def verb_phrase(adverb, verb_phrase):
|
||||
"(Adverb, VerbPhrase) -> VerbPhrase"
|
||||
#YOUR CODE GOES HERE
|
||||
|
||||
raise NotImplementedError("This function isn't done!")
|
||||
|
||||
def past_tense_transitive(verb):
|
||||
"TransitiveVerb -> PastTenseTransitiveVerb"
|
||||
#YOUR CODE GOES HERE
|
||||
|
||||
raise NotImplementedError("This function isn't done!")
|
||||
|
||||
def past_tense_intransitive(verb):
|
||||
"TransitiveVerb -> PastTenseIntransitiveVerb"
|
||||
#YOUR CODE GOES HERE
|
||||
|
||||
raise NotImplementedError("This function isn't done!")
|
||||
|
||||
def verb_phrase_transitive(verb, noun_phrase):
|
||||
"(TransitiveVerb, NounPhrase) -> VerbPhrase"
|
||||
#YOUR CODE GOES HERE
|
||||
|
||||
raise NotImplementedError("This function isn't done!")
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
|
||||
class PartOfSpeech(str):
|
||||
"A grammatical part of speech"
|
||||
|
||||
class NounPhrase(PartOfSpeech):
|
||||
"""A phrase whose head is a noun. May contain a determiner and adjectives.
|
||||
Examples: eagle, evil mountain, several delicious candies. that lady
|
||||
"""
|
||||
|
||||
class DeterminedNounPhrase(NounPhrase):
|
||||
"""A NounPhrase containing a determiner.
|
||||
Examples: my shirt, the pencil, any dog
|
||||
"""
|
||||
|
||||
class Pronoun(DeterminedNounPhrase):
|
||||
"""A noun referring to another noun.
|
||||
Examples: I, you, it, he, she
|
||||
"""
|
||||
|
||||
class Noun(NounPhrase):
|
||||
"""A base noun (which is a valid NounPhrase).
|
||||
Examples: child, pillow, chair, you
|
||||
"""
|
||||
|
||||
class PluralNoun(Noun):
|
||||
"""A noun referring to more than one of something.
|
||||
Examples: cats, houses, ideas, people
|
||||
"""
|
||||
|
||||
class VerbPhrase(PartOfSpeech):
|
||||
"""A phrase whose head is a verb, possibly containing adverbs.
|
||||
Examples: think, eat a donut, swim in the ocean
|
||||
"""
|
||||
|
||||
class TransitiveVerb(PartOfSpeech):
|
||||
"""A verb which has a subject and an object.
|
||||
(Note that many verbs may be transitive or intransitive. You can eat or eat an apple.
|
||||
Examples: annoy, cancel, chase, disobey
|
||||
"""
|
||||
|
||||
class IntransitiveVerb(VerbPhrase):
|
||||
"""A verb with a subject but no object.
|
||||
Examples: sleep, depart, run, jump
|
||||
"""
|
||||
|
||||
class PastTenseTransitiveVerb(TransitiveVerb):
|
||||
"""A transitive verb in the past tense.
|
||||
Examples: canceled, chased, disobeyed, praised
|
||||
"""
|
||||
|
||||
class PastTenseIntransitiveVerb(IntransitiveVerb):
|
||||
"""An intransitive verb in the past tense.
|
||||
Examples: slept, departed, ran, jumped
|
||||
"""
|
||||
|
||||
class Adjective(PartOfSpeech):
|
||||
"""A word which describes a noun.
|
||||
Examples: blue, ugly, tiny, late
|
||||
"""
|
||||
|
||||
class Adverb(PartOfSpeech):
|
||||
"""A word which describes a verb.
|
||||
Examples: quickly, violently, suspiciously, boldly
|
||||
"""
|
||||
|
||||
class Determiner(PartOfSpeech):
|
||||
"""A word which comes before a noun phrase and determines its referent.
|
||||
Examples: my, your, a, an, the, that, some
|
||||
"""
|
||||
|
||||
class Sentence(PartOfSpeech):
|
||||
"""A noun phrase and a verb phrase.
|
||||
Examples: I ate some old bread. The cat caught the bird. The broken chair regretted its history.
|
||||
"""
|
||||
|
||||
class Meter(str):
|
||||
"""The stress pattern of a word's syllables.
|
||||
A Meter is a string of digits. 1 means primary stress (loudest),
|
||||
2 means secondary stress (medium), and 0 means unstressed (quiet).
|
||||
For example, the meter of 'animal' is '100', the meter of 'helicopter' is '1020', and
|
||||
the meter of 'inexcusable' is '20100'.
|
||||
"""
|
||||
|
||||
def __init__(self, code):
|
||||
allowed_digits = ['0', '1', '2']
|
||||
if not all(digit in allowed_digits for digit in code):
|
||||
raise ValueError("A Meter must be a string of digits, 0-2, like '1020'")
|
||||
|
||||
class Poem(list):
|
||||
"""A Poem is just a list which can format itself nicely on multiple lines.
|
||||
"""
|
||||
def __str__(self):
|
||||
return "\n".join(str(line).capitalize() for line in self)
|
|
@ -0,0 +1,32 @@
|
|||
[[package]]
|
||||
name = "cmudict"
|
||||
version = "1.0.2"
|
||||
description = "\"A versioned python wrapper package for The CMU Pronouncing Dictionary data files.\""
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "pronouncing"
|
||||
version = "0.2.0"
|
||||
description = "A simple interface for the CMU pronouncing dictionary"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
cmudict = ">=0.4.0"
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "db19ee347247adbba13f8059b24005c899bc8232b84721a607f72aa67d9f6878"
|
||||
|
||||
[metadata.files]
|
||||
cmudict = [
|
||||
{file = "cmudict-1.0.2-py2.py3-none-any.whl", hash = "sha256:e4cb67669a5734680c1dd7368e5ffe96fabe5e9bba56bbfc5464423e48136aca"},
|
||||
{file = "cmudict-1.0.2.tar.gz", hash = "sha256:85b03098a9b45c18112f584ac2418348ecbae56b8e66f0d947bba53e4b51e5dd"},
|
||||
]
|
||||
pronouncing = [
|
||||
{file = "pronouncing-0.2.0.tar.gz", hash = "sha256:ff7856e1d973b3e16ff490c5cf1abdb52f08f45e2c35e463249b75741331e7c4"},
|
||||
]
|
|
@ -0,0 +1,68 @@
|
|||
from vocabulary import (
|
||||
get_meter,
|
||||
random_word,
|
||||
rhyming_pair,
|
||||
)
|
||||
from grammar import (
|
||||
make_indefinite,
|
||||
noun_phrase,
|
||||
past_tense_transitive,
|
||||
pluralize,
|
||||
verb_phrase,
|
||||
verb_phrase_transitive,
|
||||
)
|
||||
from grammatical_types import (
|
||||
Adjective,
|
||||
Adverb,
|
||||
Noun,
|
||||
Meter,
|
||||
Poem,
|
||||
Pronoun,
|
||||
TransitiveVerb,
|
||||
)
|
||||
|
||||
def love_poem():
|
||||
"() -> Poem"
|
||||
poem = Poem()
|
||||
poem.append("roses are red")
|
||||
poem.append("violets are blue")
|
||||
poem.append(pluralize(random_word(Noun)) + " are " + random_word(Adjective))
|
||||
poem.append("and so are you.")
|
||||
return poem
|
||||
|
||||
def couplet():
|
||||
"() -> Poem"
|
||||
poem = Poem()
|
||||
first_noun, second_noun = rhyming_pair(Noun, Noun, meter=Meter("1"))
|
||||
first_adjective = random_word(Adjective, meter=Meter("100"))
|
||||
first_np = make_indefinite(noun_phrase(first_adjective, first_noun))
|
||||
second_adjective = random_word(Adjective, meter=Meter("10"))
|
||||
second_np = make_indefinite(noun_phrase(second_adjective, second_noun))
|
||||
poem.append("I used to have " + first_np)
|
||||
poem.append("Until I swapped it for " + second_np + ".")
|
||||
return poem
|
||||
|
||||
def limerick(name, subjective_pronoun, possessive_pronoun):
|
||||
"""str, str, str -> Poem
|
||||
For example, `limerick("Chris", "he", "his")`.
|
||||
"""
|
||||
noun0, noun1 = random_word(Noun, count=2, rhymes_with=name)
|
||||
adjective = random_word(Adjective, meter=Meter("100"))
|
||||
adverb = random_word(Adverb, meter=Meter("10"))
|
||||
verb0, verb1 = rhyming_pair(TransitiveVerb, TransitiveVerb, meter=Meter("10"))
|
||||
object_pronoun = Pronoun("it")
|
||||
|
||||
np0 = make_indefinite(noun_phrase(adjective, noun0))
|
||||
np1 = make_indefinite(noun1)
|
||||
verb0 = past_tense_transitive(verb0)
|
||||
verb1 = past_tense_transitive(verb1)
|
||||
vp0 = verb_phrase(adverb, verb_phrase_transitive(verb0, object_pronoun))
|
||||
vp1 = verb_phrase_transitive(verb1, object_pronoun)
|
||||
|
||||
poem = Poem()
|
||||
poem.append("there once was a person named " + name)
|
||||
poem.append("who owned " + np0 + ".")
|
||||
poem.append(subjective_pronoun + " " + vp0 + ",")
|
||||
poem.append("and then " + subjective_pronoun + " " + vp1 + ",")
|
||||
poem.append("until it turned into " + np1 + ".")
|
||||
return poem
|
|
@ -0,0 +1,16 @@
|
|||
[tool.poetry]
|
||||
name = "mwc-pedprog-unit01-lab00"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Chris <github.com@accounts.chrisproctor.net>"]
|
||||
license = "MIT"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
pronouncing = "^0.2.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
|
@ -0,0 +1,17 @@
|
|||
## Types notes
|
||||
|
||||
For each function in the table below, describe what type the function would take as an input,
|
||||
and what type the function would return as output.
|
||||
|
||||
| Function | Input type(s) | Output type |
|
||||
|------------------------------------------------------------------|---------------|-------------|
|
||||
| Square root of a number | `float` | `float` |
|
||||
| Multiplication of two integers | | |
|
||||
| Average of a list of numbers | | |
|
||||
| A function that gives all the factors of a number | | |
|
||||
| A function to check if a word is a curse word | | |
|
||||
| A function that assigns a friendliness score to a sentence | | |
|
||||
| A function that takes a website URL and returns the page content | | |
|
||||
| A function that counts the number of sentences in a paragraph | | |
|
||||
| `>` | | |
|
||||
| `not` | | |
|
|
@ -0,0 +1,189 @@
|
|||
import json
|
||||
from pronouncing import rhymes, syllable_count, phones_for_word, search_stresses, stresses
|
||||
from random import choice, sample
|
||||
from grammatical_types import (
|
||||
Noun,
|
||||
TransitiveVerb,
|
||||
IntransitiveVerb,
|
||||
Adjective,
|
||||
Adverb,
|
||||
Meter
|
||||
)
|
||||
|
||||
def get_words():
|
||||
with open("words.json") as datafile:
|
||||
words = json.load(datafile)
|
||||
words['nouns'] = set([Noun(w) for w in words['nouns']])
|
||||
words['transitive_verbs'] = set([TransitiveVerb(w) for w in words['transitive_verbs']])
|
||||
words['intransitive_verbs'] = set([IntransitiveVerb(w) for w in words['intransitive_verbs']])
|
||||
words['adjectives'] = set([Adjective(w) for w in words['adjectives']])
|
||||
words['adverbs'] = set([Adverb(w) for w in words['adverbs']])
|
||||
return words
|
||||
|
||||
words = get_words()
|
||||
|
||||
word_types = {
|
||||
Noun: "nouns",
|
||||
TransitiveVerb: "transitive_verbs",
|
||||
IntransitiveVerb: "intransitive_verbs",
|
||||
Adjective: "adjectives",
|
||||
Adverb: "adverbs"
|
||||
}
|
||||
|
||||
def random_word(word_type, count=1, rhymes_with=None, meter=None, syllables=None):
|
||||
"""Returns one or more randomly-chosen words.
|
||||
|
||||
>>> random_word(Noun)
|
||||
'pineapple'
|
||||
>>> random_word(Adjective, syllables=4)
|
||||
'methodical'
|
||||
>>> random_word(Adverb, count=3)
|
||||
['flatly', 'gravely', 'gently']
|
||||
>>> random_word(Noun, count=4, rhymes_with="sand")
|
||||
['grand', 'command', 'band', 'brand']
|
||||
|
||||
Args:
|
||||
word_type (type): `Noun`, `TransitiveVerb`, `IntransitiveVerb`, `Adjective`, or `Adverb`.
|
||||
count (int): Optional, default `1`. Number of words to return.
|
||||
rhymes_with (str): Optional. When provided, the word(s) must rhyme.
|
||||
meter (Meter): Optional. When provided, the word(s) must match the given meter.
|
||||
syllables (int): Optional. When provided, the word(s) must have the given number of syllables.
|
||||
|
||||
Returns:
|
||||
word_type: Or a list of `word_type` when count is greater than 1.
|
||||
"""
|
||||
if word_type not in word_types.keys():
|
||||
raise TypeError("random words are not supported for type {}".format(word_type))
|
||||
assert isinstance(count, int)
|
||||
assert rhymes_with is None or isinstance(rhymes_with, str)
|
||||
assert meter is None or isinstance(meter, Meter)
|
||||
assert syllables is None or isinstance(syllables, int)
|
||||
|
||||
conditions = []
|
||||
matches = words[word_types[word_type]]
|
||||
if rhymes_with:
|
||||
conditions.append("rhymes with " + rhymes_with)
|
||||
matches = matches.intersection(map(word_type, rhymes(rhymes_with)))
|
||||
if meter:
|
||||
conditions.append("has meter " + meter)
|
||||
matches = matches.intersection(map(word_type, search_stresses('^' + meter + '$')))
|
||||
if syllables:
|
||||
conditions.append("has {} syllables".format(syllables))
|
||||
matches = matches.intersection(word for word in matches if count_syllables(word) == syllables)
|
||||
if len(matches) < count:
|
||||
target = "a " + word_type.__name__ if count == 1 else str(count) + ' ' + word_type.__name__ + 's'
|
||||
raise NoWordError("Couldn't find {} with conditions: {}".format(
|
||||
target, " and ".join(conditions)))
|
||||
return sample(tuple(matches), count) if count > 1 else choice(tuple(matches))
|
||||
|
||||
def rhyming_pair(first_word_type, second_word_type, meter=None, syllables=None, attempts=20):
|
||||
"""Returns a pair of words which rhyme.
|
||||
|
||||
>>> rhyming_pair(Noun, Adjective, syllables=2)
|
||||
('traffic', 'graphic')
|
||||
>>> rhyming_pair(TransitiveVerb, IntransitiveVerb, meter="10")
|
||||
('fumble', 'grumble')
|
||||
|
||||
Args:
|
||||
first_word_type (type): `Noun`, `TransitiveVerb`, `IntransitiveVerb`, `Adjective`, or `Adverb`.
|
||||
second_word_type (type): `Noun`, `TransitiveVerb`, `IntransitiveVerb`, `Adjective`, or `Adverb`.
|
||||
meter (Meter): Optional. A stress pattern for syllables like "01" (ad MIRE) or "100" (SYM phon y)
|
||||
syllables (int): Optional. The number of syllables in each word.
|
||||
attempts (int): Optional, default is `20`. Number of times to try before giving up.
|
||||
This function chooses the first word first, and then looks for a matching second word. If there
|
||||
is no match, we need to throw away the first word and try again. Without a limit on the number
|
||||
of attempts, this function would potentially search forever, locking up your program.
|
||||
The default value of `attempts` should be fine, but you can tune it if you want.
|
||||
|
||||
Returns:
|
||||
(first_word_type, second_word_type)
|
||||
"""
|
||||
if first_word_type not in word_types.keys():
|
||||
raise TypeError("random words are not supported for type {}".format(first_word_type))
|
||||
if second_word_type not in word_types.keys():
|
||||
raise TypeError("random words are not supported for type {}".format(second_word_type))
|
||||
assert meter is None or isinstance(meter, Meter)
|
||||
assert syllables is None or isinstance(syllables, int)
|
||||
assert isinstance(attempts, int)
|
||||
|
||||
for i in range(attempts):
|
||||
try:
|
||||
first = random_word(first_word_type, meter=meter, syllables=syllables)
|
||||
second = random_word(second_word_type, rhymes_with=first, meter=meter, syllables=syllables)
|
||||
return first, second
|
||||
except NoWordError:
|
||||
continue
|
||||
conditions = []
|
||||
if meter:
|
||||
conditions.append("has meter " + meter)
|
||||
if syllables:
|
||||
conditions.append("has {} syllables".format(syllables))
|
||||
raise NoWordError("Couldn't find a rhyming {} and {} with {}".format(
|
||||
first_word_type.__name__, second_word_type.__name__,
|
||||
" and ".join(conditions)))
|
||||
|
||||
def count_syllables(word):
|
||||
"""Counts the number of syllables in a word.
|
||||
|
||||
>>> count_syllables("bogus")
|
||||
2
|
||||
|
||||
Args:
|
||||
word (str)
|
||||
|
||||
Returns:
|
||||
int: The number of syllables in the word.
|
||||
"""
|
||||
phones = phones_for_word(word)
|
||||
if len(phones) > 0:
|
||||
return syllable_count(phones[0])
|
||||
|
||||
def get_meter(word):
|
||||
"""Returns the word's meter, or its pattern of stresses when the word is spoken.
|
||||
Meter can be used to write poetry with a beat.
|
||||
The meter is a string of digits. 1 means primary stress (loudest),
|
||||
2 means secondary stress (medium), and 0 means unstressed (quiet).
|
||||
For example:
|
||||
|
||||
>>> get_meter("animal")
|
||||
'100'
|
||||
>>> get_meter("helicopter")
|
||||
'1020'
|
||||
>>> get_meter("inexcusable")
|
||||
'20100'
|
||||
|
||||
Args:
|
||||
word (str)
|
||||
|
||||
Returns:
|
||||
Meter
|
||||
"""
|
||||
phones = phones_for_word(word)
|
||||
if not phones:
|
||||
raise NoWordError("Could not pronounce " + word)
|
||||
return Meter(stresses(phones[0]))
|
||||
|
||||
def starts_with_vowel_sound(text):
|
||||
"""Checks whether the text starts with a vowel sound.
|
||||
Useful for determining whether to use 'a' or 'an' as an
|
||||
indefinite article.
|
||||
|
||||
>>> starts_with_vowel_sound("horrible hounds")
|
||||
False
|
||||
>>> starts_with_vowel_sound("awesome owls")
|
||||
True
|
||||
|
||||
Args:
|
||||
text (str): One or more words.
|
||||
|
||||
Returns:
|
||||
bool: `True` if the text starts with
|
||||
"""
|
||||
first_word = text.split(" ")[0]
|
||||
vowel_phones = ["AA", "AE", "AH", "AO", "AW", "AY", "EH", "ER", "EY", "IY", "IH", "OW", "OY", "UH", "UW"]
|
||||
phones = phones_for_word(first_word)
|
||||
return len(phones) > 0 and phones[0].split(' ')[0][:2] in vowel_phones
|
||||
|
||||
class NoWordError(Exception):
|
||||
"This error means no word(s) matched the search."
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue