Initial commit

This commit is contained in:
Chris 2022-02-27 18:51:59 -05:00
commit 370415e29b
9 changed files with 523 additions and 0 deletions

28
README.md Normal file
View File

@ -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`.

79
grammar.py Normal file
View File

@ -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!")

93
grammatical_types.py Normal file
View File

@ -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)

32
poetry.lock generated Normal file
View File

@ -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"},
]

68
poetry.py Normal file
View File

@ -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

16
pyproject.toml Normal file
View File

@ -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"

17
types_notes.md Normal file
View File

@ -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` | | |

189
vocabulary.py Normal file
View File

@ -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."

1
words.json Normal file

File diff suppressed because one or more lines are too long