Add rhyme generation helpers
parent
731ac8bd03
commit
ff17cdec32
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,192 @@
|
||||
(ns com.owoga.prhyme.gen
|
||||
(:require [clojure.string :as string]
|
||||
[com.owoga.prhyme.util.weighted-rand :as weighted-rand]
|
||||
[com.owoga.prhyme.util.nlp :as nlp]
|
||||
[com.owoga.prhyme.frp :as frp]
|
||||
[com.owoga.prhyme.core :as prhyme]))
|
||||
|
||||
(def words-map
|
||||
(into {} (map #(vector (string/lower-case (:word %)) %) frp/words)))
|
||||
|
||||
(defn merge-phrase-words
|
||||
"Given multiple `Word`, like the words for 'well off', create a single `Word`
|
||||
that is syllabified as ('well' 'off') rather than as the combined ('weh'
|
||||
'loff'). Useful for finding single-word rhymes of multiple-word targets.
|
||||
|
||||
An example: 'war on crime' -> 'turpentine'."
|
||||
[phrase phrase-words]
|
||||
(loop [merged (first phrase-words)
|
||||
phrase-words (rest phrase-words)]
|
||||
(cond
|
||||
(and (empty? phrase-words) (empty? merged)) nil
|
||||
(empty? phrase-words) (assoc merged :word phrase)
|
||||
:else (recur (-> merged
|
||||
(assoc :syllables (concat (:syllables merged)
|
||||
(:syllables (first phrase-words))))
|
||||
(assoc :syllable-count (+ (:syllable-count merged)
|
||||
(:syllable-count (first phrase-words))))
|
||||
(assoc :rimes (concat (:rimes merged)
|
||||
(:rimes (first phrase-words))))
|
||||
(assoc :onsets (concat (:onsets merged)
|
||||
(:onsets (first phrase-words))))
|
||||
(assoc :nuclei (concat (:nuclei merged)
|
||||
(:nuclei (first phrase-words)))))
|
||||
(rest phrase-words)))))
|
||||
|
||||
(defn phrase->word
|
||||
"Given a word like 'well-off' or a phrase like 'war on poverty', return a Word
|
||||
that has the correct syllables, rimes, onsets, and nucleus. This way we can
|
||||
rhyme against phrases that aren't in the dictionary, as long as the words that
|
||||
make up the phrase are in the dictionary. Returns nil if the word is not in
|
||||
the dictionary."
|
||||
[words phrase]
|
||||
(->> (string/split phrase #"[ -]")
|
||||
(map (fn [phrase-word]
|
||||
(let [word (first (filter (fn [word]
|
||||
(= phrase-word (:norm-word word)))
|
||||
words))]
|
||||
(when (nil? word)
|
||||
(throw (ex-info "Word not found in dictionary." {:word phrase-word})))
|
||||
word)))
|
||||
(merge-phrase-words phrase)))
|
||||
|
||||
(defn adjust-for-markov
|
||||
[markov percent]
|
||||
(fn [[words target result]]
|
||||
(let [markov-options (markov (list (:norm-word (first result))))
|
||||
markov-option-avg (/ (apply + (vals markov-options))
|
||||
(max 1 (count markov-options)))]
|
||||
(if (nil? markov-options)
|
||||
[words target result]
|
||||
(let [[markovs non-markovs]
|
||||
((juxt filter remove)
|
||||
#(markov-options (:norm-word %))
|
||||
words)
|
||||
weight-non-markovs (apply + (map :weight non-markovs))
|
||||
target-weight-markovs (* 100 percent weight-non-markovs)
|
||||
count-markovs (count markovs)
|
||||
adjustment-markovs (if (= 0 count-markovs) 1 (/ target-weight-markovs count-markovs))]
|
||||
[(concat
|
||||
(map
|
||||
(fn [m]
|
||||
(let [option (markov-options (:norm-word m))]
|
||||
(as-> m m
|
||||
(assoc m :weight (* (/ option markov-option-avg) adjustment-markovs (:weight m)))
|
||||
(assoc m :adjustment-for-markov (* (/ option markov-option-avg) adjustment-markovs)))))
|
||||
markovs)
|
||||
non-markovs)
|
||||
target
|
||||
result])))))
|
||||
|
||||
(defn adjust-for-rimes
|
||||
[target-rime dictionary percent]
|
||||
(fn [[words target result]]
|
||||
(let [words-with-rime-count
|
||||
(map
|
||||
(fn [word]
|
||||
(assoc word :num-matching (count (frp/consecutive-matching target word :rimes))))
|
||||
words)
|
||||
|
||||
[rhyming non-rhyming]
|
||||
((juxt filter remove)
|
||||
#(< 0 (:num-matching %))
|
||||
words-with-rime-count)
|
||||
|
||||
weight-non-rhyming (apply + (map :weight non-rhyming))
|
||||
target-weight-rhyming (* 100 percent weight-non-rhyming)
|
||||
count-rhyming (count rhyming)
|
||||
adjustment-rhyming (if (= 0 count-rhyming) 1 (/ target-weight-rhyming count-rhyming))]
|
||||
[(concat
|
||||
(map
|
||||
(fn [word]
|
||||
(as-> word word
|
||||
(assoc word :weight (* adjustment-rhyming (:weight word)))
|
||||
(assoc word :adjustment-for-rimes adjustment-rhyming)))
|
||||
rhyming)
|
||||
non-rhyming)
|
||||
target
|
||||
result])))
|
||||
|
||||
(defn prhyme
|
||||
"2020-10-21 iteration"
|
||||
[words weights-adjuster target stop?]
|
||||
(let [target (assoc target :original-syllables (:syllables target))
|
||||
words (map #(assoc % :weight 1) words)]
|
||||
(loop [target target
|
||||
result '()
|
||||
sentinel 0]
|
||||
(if (or (stop? target result)
|
||||
(> sentinel 5))
|
||||
result
|
||||
(let [[weighted-words _ _] (weights-adjuster [words target result])
|
||||
rng (weighted-rand/from-weights (map :weight weighted-words))
|
||||
index (weighted-rand/nextr rng nil)
|
||||
selection (nth weighted-words index)
|
||||
new-target (->> target
|
||||
(#(assoc % :syllables (drop-last
|
||||
(:syllable-count
|
||||
selection)
|
||||
(:syllables
|
||||
target))))
|
||||
(#(assoc % :rimes (prhyme/rimes (:syllables %))))
|
||||
(#(assoc % :onsets (prhyme/onset+nucleus (:syllables %))))
|
||||
(#(assoc % :nuclei (prhyme/nucleus (:syllables %)))))
|
||||
result (cons selection result)]
|
||||
(recur new-target result (inc sentinel)))))))
|
||||
|
||||
(defn prhymer [words weights-adjuster target stop]
|
||||
(cons (prhyme
|
||||
words
|
||||
weights-adjuster
|
||||
target
|
||||
stop)
|
||||
(lazy-seq (prhymer words weights-adjuster target stop))))
|
||||
|
||||
(defn sentence-stop [target]
|
||||
(fn [inner-target result]
|
||||
(let [result-sentence (string/join " " (map :norm-word result))]
|
||||
(when-not (empty? result)
|
||||
(or (nlp/valid-sentence? result-sentence)
|
||||
(< (:syllable-count target)
|
||||
(apply + (map :syllable-count result)))
|
||||
(< 5 (count result)))))))
|
||||
|
||||
(defn gen-prhymes [words markov poem-lines]
|
||||
(let [words (map #(assoc % :weight 1) words)
|
||||
words-map (into {} (map #(vector (:norm-word %) %) words))]
|
||||
(map (fn [line]
|
||||
(let [target (frp/phrase->word words line)
|
||||
stop (sentence-stop target)
|
||||
weights-adjuster (comp (adjust-for-markov markov 0.9)
|
||||
(adjust-for-rimes target words-map 0.9))
|
||||
r (prhymer words weights-adjuster target stop)]
|
||||
(string/join " " (map #(:norm-word %) (first r)))))
|
||||
poem-lines)))
|
||||
|
||||
|
||||
|
||||
(comment
|
||||
(take 3 frp/words)
|
||||
(phrase->word frp/popular "well-off")
|
||||
(map (fn [line] (phrase->word frp/popular line))
|
||||
["mister sandman"
|
||||
"give me dream"
|
||||
"make him the cutest"
|
||||
"that have ever seen"])
|
||||
(defonce lovecraft-markov (read-string (slurp "lovecraft.edn")))
|
||||
(def adj (comp (adjust-for-markov lovecraft-markov 0.99)))
|
||||
(gen-prhymes frp/popular
|
||||
lovecraft-markov
|
||||
["mister sandman"
|
||||
"give me the dream"
|
||||
"make him the cutest"
|
||||
"that eye have ever seen"])
|
||||
(repeatedly 20 #(gen-prhymes frp/popular lovecraft-markov ["mister sandman"]))
|
||||
|
||||
(let [target (frp/phrase->word frp/words "i solemnly swear i am up to no good")
|
||||
words (map #(assoc % :weight 1) frp/popular)
|
||||
weights-adjuster (comp (adjust-for-markov lovecraft-markov 0.9)
|
||||
(adjust-for-rimes target words-map 0.9))
|
||||
stop (sentence-stop target)
|
||||
r (prhymer words weights-adjuster target stop)]
|
||||
(map (fn [p] (string/join " " (map #(:norm-word %) p))) (take 5 r))))
|
Loading…
Reference in New Issue