Skip to content

Instantly share code, notes, and snippets.

@benhardy
Created November 7, 2017 16:26
Show Gist options
  • Save benhardy/3355f9a5278a3dd5952013cc09178334 to your computer and use it in GitHub Desktop.
Save benhardy/3355f9a5278a3dd5952013cc09178334 to your computer and use it in GitHub Desktop.
A little experiment in parsing English and answering questions
/** kinds of things that can make up a sentence */
sealed trait Term {
def show: String
def complexity: Int = 1
def root: Term = this
}
case class Word(show: String) extends Term
trait NounTerm extends Term
trait VerbTerm extends Term
trait AdjectiveTerm extends Term
trait AdverbTerm extends Term
trait InterrogativeTerm extends Term
case class Noun(show: String) extends NounTerm
case class Adjective(show: String) extends AdjectiveTerm
case class Verb(show: String) extends VerbTerm
case class Adverb(show: String) extends AdverbTerm
case class Interrogative(show: String) extends InterrogativeTerm
case class NounDescribed(adj:AdjectiveTerm, noun: NounTerm) extends NounTerm {
override def show = adj.show + " " + noun.show
override def complexity = adj.complexity + noun.complexity
override def root = noun
}
case class VerbDescribed(adverb: AdverbTerm, verb: VerbTerm) extends VerbTerm {
override def show = adverb.show + " " + verb.show
override def complexity = adverb.complexity + verb.complexity
override def root = verb
}
case class Statement(subject:NounTerm, verb:VerbTerm, obj:NounTerm) extends Term {
override def show = subject.show + " " + verb.show + " " + obj.show
override def complexity = subject.complexity + verb.complexity + obj.complexity
}
case class Question(interrogative: Interrogative, fact: Statement) extends Term {
override def show = interrogative.show + " " + fact.show
override def complexity = interrogative.complexity + fact.complexity
}
object Vocabulary {
val noun = "cat,dog,chicken,horse,monkey".split(",").toSet
val verb = "like,dislike,is,turn".split(",").toSet
val adjs = "blue,delicious,hungry,big,dead".split(",").toSet
val advs = "quickly,slowly,really,totally".split(",").toSet
val interrogative = "who,does,which,what".split(",").toSet
}
object Parser {
def parse(sentence: String): Term = {
val words = classifyWords(sentence)
val collapsed = words.foldRight(List.empty[Term])(assembleStructure)
collapsed.maxBy(_.complexity)
}
private def classifyWords(sentence: String): List[Term] = {
sentence.split(" ").toList.map {
case word if Vocabulary.noun.contains(word) => Noun(word)
case word if Vocabulary.adjs.contains(word) => Adjective(word)
case word if Vocabulary.advs.contains(word) => Adverb(word)
case word if Vocabulary.verb.contains(word) => Verb(word)
case word if Vocabulary.interrogative.contains(word) => Interrogative(word)
case word => Word(word)
}
}
private def assembleStructure(head: Term, tail: List[Term]) = {
(head, tail) match {
case (adj: AdjectiveTerm, (noun: NounTerm) :: rest) => NounDescribed(adj, noun) :: rest
case (adv: AdverbTerm, (verb: VerbTerm) :: rest) => VerbDescribed(adv, verb) :: rest
case (subject: NounTerm, (verb: VerbTerm) :: (obj: NounTerm) :: rest) => {
Statement(subject, verb, obj) :: rest
}
case (q: Interrogative, (statement: Statement) :: rest) => {
Question(q, statement) :: rest
}
case (Interrogative("what"), Interrogative("does") :: (subject: NounTerm) :: (verb: VerbTerm) :: rest) => {
Question(Interrogative("what"), Statement(subject, verb, Noun("?"))) :: rest
}
case (Interrogative("who"), (verb: VerbTerm) :: (obj: NounTerm) :: rest) => {
Question(Interrogative("who"), Statement(Noun("?"), verb, obj)) :: rest
}
case _ => head :: tail
}
}
}
object Knowledge {
val facts = List(
"cat really like blue chicken",
"dog really like blue chicken"
).map(Parser.parse)
def query(questionFull: Question): List[String] = {
facts flatMap { known =>
(questionFull, known) match {
// exact match of question and fact
case (Question(Interrogative("does"), questionFact), fact)
if questionFact equals fact
=> List("YES")
// the question object noun is more general than the fact's, so display the fact's object noun
case (Question(Interrogative("does"), question), fact: Statement)
if (question.subject == fact.subject) &&
(question.verb == fact.verb) &&
(question.obj == fact.obj.root)
=> List(s"Yes, if it's ${fact.obj.show}")
// the question verb is more general than the fact one, so answer with the fact's specific verb
case (Question(Interrogative("does"), question), fact: Statement)
if (question.subject == fact.subject) &&
(question.verb == fact.verb.root) &&
(question.obj == fact.obj)
=> List(s"Yes, ${fact.verb.show}")
// value query
case (Question(Interrogative("what"), Statement(qSubject, qVerb, Noun("?"))), fact: Statement)
if (qSubject == fact.subject) &&
(qVerb == fact.verb)
=> List(fact.obj.show)
// subject query
case (Question(Interrogative("who"), Statement(Noun("?"), qVerb, qObject)), fact: Statement)
if (qObject == fact.obj) &&
(qVerb == fact.verb)
=> List(fact.subject.show)
case _ => Nil
}
}
}
}
object TheOracle {
val questions = List(
"does cat really like blue chicken",
"does cat really like chicken",
"does cat like chicken",
"does cat like blue chicken",
"what does cat really like",
"who really like blue chicken"
).map(Parser.parse)
def main(args: Array[String]) = {
val responses = questions.map {
case (question: Question) => question.show + "?\n" + Knowledge.query(question).map(" * " + _).mkString("\n")
case _ => ""
}
println(responses.mkString("\n\n"))
}
}
/*
This produces the following output:
does cat really like blue chicken?
* YES
does cat really like chicken?
* Yes, if it's blue chicken
does cat like chicken?
does cat like blue chicken?
* Yes, really like
what cat really like ??
* blue chicken
who ? really like blue chicken?
* cat
* dog
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment