Skip to content

Instantly share code, notes, and snippets.

@drusepth
Created September 8, 2017 21:31
Show Gist options
  • Select an option

  • Save drusepth/0cd4798a34ee3303c7944042064411ec to your computer and use it in GitHub Desktop.

Select an option

Save drusepth/0cd4798a34ee3303c7944042064411ec to your computer and use it in GitHub Desktop.
# <`lm`> if a supernatural chicken was haunting you, would that be a poultrygeist?
# <dru> `lm`: I feel like I could set up a pun generator to find words that are a maximum levenshtein distance away
# from a compound word stem (e.g. poultry --> [polter]geist)
# <dru> I have no idea how to set up the rest of the joke other than manually though
# <dru> "what do you call a [adjective related to poltergeist] [synonym for chicken | chicken] that [verb related
# to poltergeist]?"
# <dru> thanks for the inspiration bbl
# Intended joke outcome:
# Q. What do you call a supernatural chicken haunting you?
# A. A poultrygeist!
# Process:
# 1. Generate core pun word: "chicken" # core_word: "chicken"
# 2. Look up all synonyms/typeOfs for core word # synonyms: ["fowl", "poultry"]
# 3. Find another noun a low L-distance away from one # related_word: "poultergeist"
# - This will probably be the bulk of process time
# 4. Look up adjective that can describe related_word # related_adjective: "supernatural"
# - This step can be ommitted without ruining the joke.
# 5. Look up a verb that is often used to describe related_word # related_verb: "haunt"
# 6. Choose a random joke template using these tokens # template: "Q. What do you call a _ _ _ you? / A. A _!"
# - This will probably be moved to a higher step once multiple templates require different tokens.
# 7. Prepare the punchline / generate our pun!
# 8. Token replacement for the best joke in the world
require 'unirest'
require 'levenshtein-ffi' # using a native C binding here so comparisons are faster
require 'pry'
MASHAPE_API_KEY = ENV['MASHAPE_API_KEY']
ALLOW_API_CALLS = true
class Log
INFO_ENABLED = true
DEBUG_ENABLED = false
def self.info message; puts "[INFO ] #{message}" if INFO_ENABLED; end
def self.debug message; puts "[DEBUG] #{message}" if DEBUG_ENABLED; end
end
class Dictionary
# TODO: almost all of this -- probably want to use a data structure that makes sorting by Levenshtein distance easier
attr_accessor :words
def initialize(filepath)
# TODO: load real wordlist from file, organize, etc
self.words = %w(
apple bottom carrot duck elephant fauna god hillary ice jackal koala llama monkey neutral
orange poultergeist quiet rest six teem unary wide xiv yes zoo
)
end
end
class Word
attr_accessor :word, :data
def initialize(word)
self.word = word
populate_data if ALLOW_API_CALLS
end
def common_adjectives
response = Unirest.get(
"https://api.datamuse.com/words?rel_jjb=#{self.word}",
headers: { "Accept" => "application/json" }
).body
# TODO: need a new datasource with more adjectives -- this one doesn't have ANY FOR POULTERGEIST!
response = mock_poultergeist_response if self.word == 'poultergeist'
# Pull out just the words; we don't care about frequency scores
response.map { |word_object| word_object['word'] }
end
def common_verbs
# TODO: all of this -- probably need to build out some custom endpoints on Retort to fetch data for:
# "give me all verbs we've seen a given noun doing".
['haunt', 'spook', 'chill', 'follow']
end
private
def populate_data
Log.info("Making API request for #{self.word} word data")
self.data = fetch_word_data
Log.debug("Response: #{self.data}")
mock_api_response if self.data == "Too many requests. Please try again."
end
def fetch_word_data
res = Unirest.get "https://wordsapiv1.p.mashape.com/words/#{self.word}",
headers: {
"X-Mashape-Key" => MASHAPE_API_KEY,
"Accept" => "application/json"
}
res.body
end
def mock_api_response
Log.debug "API is overloaded; falling back on mocks for #{self.word}."
case self.word
when 'chicken'; self.data = mock_chicken_response
else; Log.debug "No mock defined for #{self.word}."
end
end
def mock_chicken_response
{
"word": "chicken",
"results": [
{
"definition": "a domestic fowl bred for flesh or eggs; believed to have been developed from the red jungle fowl",
"partOfSpeech": "noun",
"synonyms": [
"gallus gallus"
],
"typeOf": [
"fowl",
"poultry",
"domestic fowl"
],
"hasTypes": [
"hen",
"capon",
"chick",
"dominick",
"biddy",
"spring chicken",
"cock",
"rhode island red",
"rooster",
"dominique",
"orpington"
],
"hasParts": [
"poulet",
"volaille"
]
},
{
"definition": "easily frightened",
"partOfSpeech": "adjective",
"synonyms": [
"chickenhearted",
"lily-livered",
"white-livered",
"yellow",
"yellow-bellied"
],
"usageOf": [
"colloquialism"
],
"similarTo": [
"fearful",
"cowardly"
]
},
{
"definition": "a person who lacks confidence, is irresolute and wishy-washy",
"partOfSpeech": "noun",
"synonyms": [
"crybaby",
"wimp"
],
"typeOf": [
"doormat",
"weakling",
"wuss"
]
},
{
"definition": "the flesh of a chicken used for food",
"partOfSpeech": "noun",
"synonyms": [
"poulet",
"volaille"
],
"typeOf": [
"poultry"
],
"hasTypes": [
"pullet",
"fryer",
"roaster",
"frier",
"spatchcock",
"hen",
"capon",
"broiler"
],
"hasParts": [
"chicken wing",
"white meat",
"breast"
],
"partOf": [
"gallus gallus"
]
},
{
"definition": "a foolhardy competition; a dangerous activity that is continued until one competitor becomes afraid and stops",
"partOfSpeech": "noun",
"typeOf": [
"contest",
"competition"
]
}
],
"syllables": {
"count": 2,
"list": [
"chick",
"en"
]
},
"pronunciation": {
"all": "'ʧɪkən"
},
"frequency": 4.8
}
end
def mock_poultergeist_response
[
{ 'word' => 'friendly', 'score' => 1.0 },
{ 'word' => 'spooky', 'score' => 0.8 },
{ 'word' => 'supernatural', 'score' => 0.5 },
{ 'word' => 'blazing', 'score' => 420 }
]
end
end
class Verb < Word
attr_accessor :word
def initialize(word)
self.word = word
end
def present_participle_form
self.word + 'ing' #lol
end
end
# 1. Generate core pun word: "chicken" # core_word: "chicken"
core_word = Word.new("chicken") # or Word.new(random_noun)
# 2. Look up all synonyms/typeOfs for core word # synonyms: ["fowl", "poultry"]
# synonyms = core_word.data['results']
# .select { |word| word['partOfSpeech'] == 'noun' }
# .flat_map { |word| word['typeOf'] }
selected_synonym = "poultry" # todo loop over each synonym so we can fall back / continue
# 3. Find another noun a low L-distance away from one # related_word: "poultergeist"
dictionary = Dictionary.new("one_day_ill_use_a_real_wordlist.txt")
related_word_raw_string = dictionary.words
.sort_by do |word|
minimum_distance = selected_synonym.length
iteratable_chunk_size = [selected_synonym.length, word.length].min
word.chars.each_cons(iteratable_chunk_size) do |character_sequence|
levenshtein_distance = Levenshtein.distance(character_sequence.join, selected_synonym)
Log.debug "\t#{character_sequence.join} => #{levenshtein_distance} (minimum #{minimum_distance} for #{word})"
if levenshtein_distance < minimum_distance
minimum_distance = levenshtein_distance
end
end
minimum_distance
end
.first
related_word = Word.new(related_word_raw_string)
# 4. Look up adjective that can describe related_word # related_adjective: "supernatural"
related_adjective = related_word.common_adjectives.sample
# 5. Look up a verb that is often used to describe related_word # related_verb: "haunt"
related_verb = Verb.new(related_word.common_verbs.sample).present_participle_form
# 6. Choose a random joke template
joke_template = [
"Q. What do you call a <related_adjective> <core_word> <related_verb> you?",
"A. A <pun>!"
].join("\n")
# 7. Prepare the PUNchline # punchline: "poultrygeist"
# 7a. Find the related_word subsequence most similar to our selected_synonym
iteratable_chunk_size = [selected_synonym.length, related_word.word.length].min
most_similar_subsequence = related_word.word[0, iteratable_chunk_size]
best_subsequence_score = selected_synonym.length
related_word.word.chars.each_cons(iteratable_chunk_size) do |character_sequence|
levenshtein_distance = Levenshtein.distance(character_sequence.join, selected_synonym)
if levenshtein_distance < best_subsequence_score
best_subsequence_score = levenshtein_distance
most_similar_subsequence = character_sequence.join
end
end
# 7b. After finding the sequence to swap our pun into, replace it inline with our selected_synonym
pun = related_word.word.dup
pun[most_similar_subsequence] = selected_synonym
# 8. Token replacement for the best joke in the world
joke = joke_template
.gsub('<related_adjective>', related_adjective)
.gsub('<core_word>', core_word.word)
.gsub('<related_verb>', related_verb)
.gsub('<pun>', pun)
puts "-"*20, joke
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment