Class: MegaHAL

Inherits:
Object
  • Object
show all
Defined in:
lib/megahal/keyword.rb,
lib/megahal/megahal.rb

Constant Summary collapse

GREETING =
["G'DAY", "GREETINGS", "HELLO", "HULLO", "HI", "HOWDY", "WELCOME"]
ANTONYMS =
[
  ["DISLIKE", "LIKE"],
  ["HATE", "LOVE"],
  ["I", "YOU"],
  ["I'D", "YOU'D"],
  ["I'LL", "YOU'LL"],
  ["I'M", "YOU'RE"],
  ["I'VE", "YOU'VE"],
  ["LIKE", "DISLIKE"],
  ["LOVE", "HATE"],
  ["ME", "YOU"],
  ["MINE", "YOURS"],
  ["MY", "YOUR"],
  ["MYSELF", "YOURSELF"],
  ["NO", "YES"],
  ["WHY", "BECAUSE"],
  ["YES", "NO"],
  ["YOU", "I"],
  ["YOU", "ME"],
  ["YOU'D", "I'D"],
  ["YOU'LL", "I'LL"],
  ["YOU'RE", "I'M"],
  ["YOU'VE", "I'VE"],
  ["YOUR", "MY"],
  ["YOURS", "MINE"],
  ["YOURSELF", "MYSELF"],
  ["HOLMES", "WATSON"],
  ["FRIEND", "ENEMY"],
  ["ALIVE", "DEAD"],
  ["LIFE", "DEATH"],
  ["QUESTION", "ANSWER"],
  ["BLACK", "WHITE"],
  ["COLD", "HOT"],
  ["HAPPY", "SAD"],
  ["FALSE", "TRUE"],
  ["HEAVEN", "HELL"],
  ["GOD", "DEVIL"],
  ["NOISY", "QUIET"],
  ["WAR", "PEACE"],
  ["SORRY", "APOLOGY"]
]
SWAP =
AUXILIARY =
"  DISLIKE\n  HE\n  HER\n  HERS\n  HIM\n  HIS\n  I\n  I'D\n  I'LL\n  I'M\n  I'VE\n  LIKE\n  ME\n  MINE\n  MY\n  MYSELF\n  ONE\n  SHE\n  THREE\n  TWO\n  YOU\n  YOU'D\n  YOU'LL\n  YOU'RE\n  YOU'VE\n  YOUR\n  YOURS\n  YOURSELF\n".each_line.to_a.map(&:strip)
BANNED =
"  A\n  ABILITY\n  ABLE\n  ABOUT\n  ABSOLUTE\n  ABSOLUTELY\n  ACROSS\n  ACTUAL\n  ACTUALLY\n  AFTER\n  AFTERNOON\n  AGAIN\n  AGAINST\n  AGO\n  AGREE\n  ALL\n  ALMOST\n  ALONG\n  ALREADY\n  ALTHOUGH\n  ALWAYS\n  AM\n  AN\n  AND\n  ANOTHER\n  ANY\n  ANYHOW\n  ANYTHING\n  ANYWAY\n  ARE\n  AREN'T\n  AROUND\n  AS\n  AT\n  AWAY\n  BACK\n  BAD\n  BE\n  BEEN\n  BEFORE\n  BEHIND\n  BEING\n  BELIEVE\n  BELONG\n  BEST\n  BETTER\n  BETWEEN\n  BIG\n  BIGGER\n  BIGGEST\n  BIT\n  BOTH\n  BUDDY\n  BUT\n  BY\n  CALL\n  CALLED\n  CALLING\n  CAME\n  CAN\n  CAN'T\n  CANNOT\n  CARE\n  CARING\n  CASE\n  CATCH\n  CAUGHT\n  CERTAIN\n  CERTAINLY\n  CHANGE\n  CLOSE\n  CLOSER\n  COME\n  COMING\n  COMMON\n  CONSTANT\n  CONSTANTLY\n  COULD\n  CURRENT\n  DAY\n  DAYS\n  DERIVED\n  DESCRIBE\n  DESCRIBES\n  DETERMINE\n  DETERMINES\n  DID\n  DIDN'T\n  DO\n  DOES\n  DOESN'T\n  DOING\n  DON'T\n  DONE\n  DOUBT\n  DOWN\n  EACH\n  EARLIER\n  EARLY\n  ELSE\n  ENJOY\n  ESPECIALLY\n  EVEN\n  EVER\n  EVERY\n  EVERYBODY\n  EVERYONE\n  EVERYTHING\n  FACT\n  FAIR\n  FAIRLY\n  FAR\n  FELLOW\n  FEW\n  FIND\n  FINE\n  FOR\n  FORM\n  FOUND\n  FROM\n  FULL\n  FURTHER\n  GAVE\n  GET\n  GETTING\n  GIVE\n  GIVEN\n  GIVING\n  GO\n  GOING\n  GONE\n  GOOD\n  GOT\n  GOTTEN\n  GREAT\n  HAD\n  HAS\n  HASN'T\n  HAVE\n  HAVEN'T\n  HAVING\n  HELD\n  HERE\n  HIGH\n  HOLD\n  HOLDING\n  HOW\n  IF\n  IN\n  INDEED\n  INSIDE\n  INSTEAD\n  INTO\n  IS\n  ISN'T\n  IT\n  IT'S\n  ITS\n  JUST\n  KEEP\n  KIND\n  KNEW\n  KNOW\n  KNOWN\n  LARGE\n  LARGER\n  LARGETS\n  LAST\n  LATE\n  LATER\n  LEAST\n  LESS\n  LET\n  LET'S\n  LEVEL\n  LIKES\n  LITTLE\n  LONG\n  LONGER\n  LOOK\n  LOOKED\n  LOOKING\n  LOOKS\n  LOW\n  MADE\n  MAKE\n  MAKING\n  MANY\n  MATE\n  MAY\n  MAYBE\n  MEAN\n  MEET\n  MENTION\n  MERE\n  MIGHT\n  MOMENT\n  MORE\n  MORNING\n  MOST\n  MOVE\n  MUCH\n  MUST\n  NEAR\n  NEARER\n  NEVER\n  NEXT\n  NICE\n  NOBODY\n  NONE\n  NOON\n  NOONE\n  NOT\n  NOTE\n  NOTHING\n  NOW\n  OBVIOUS\n  OF\n  OFF\n  ON\n  ONCE\n  ONLY\n  ONTO\n  OPINION\n  OR\n  OTHER\n  OUR\n  OUT\n  OVER\n  OWN\n  PART\n  PARTICULAR\n  PARTICULARLY\n  PERHAPS\n  PERSON\n  PIECE\n  PLACE\n  PLEASANT\n  PLEASE\n  POPULAR\n  PREFER\n  PRETTY\n  PUT\n  QUITE\n  REAL\n  REALLY\n  RECEIVE\n  RECEIVED\n  RECENT\n  RECENTLY\n  RELATED\n  RESULT\n  RESULTING\n  RESULTS\n  SAID\n  SAME\n  SAW\n  SAY\n  SAYING\n  SEE\n  SEEM\n  SEEMED\n  SEEMS\n  SEEN\n  SELDOM\n  SENSE\n  SET\n  SEVERAL\n  SHALL\n  SHORT\n  SHORTER\n  SHOULD\n  SHOW\n  SHOWS\n  SIMPLE\n  SIMPLY\n  SMALL\n  SO\n  SOME\n  SOMEONE\n  SOMETHING\n  SOMETIME\n  SOMETIMES\n  SOMEWHERE\n  SORT\n  SORTS\n  SPEND\n  SPENT\n  STILL\n  STUFF\n  SUCH\n  SUGGEST\n  SUGGESTION\n  SUPPOSE\n  SURE\n  SURELY\n  SURROUND\n  SURROUNDS\n  TAKE\n  TAKEN\n  TAKING\n  TELL\n  THAN\n  THANK\n  THANKS\n  THAT\n  THAT'S\n  THATS\n  THE\n  THEIR\n  THEM\n  THEN\n  THERE\n  THEREFORE\n  THESE\n  THEY\n  THING\n  THINGS\n  THIS\n  THOSE\n  THOUGH\n  THOUGHTS\n  THOUROUGHLY\n  THROUGH\n  TINY\n  TO\n  TODAY\n  TOGETHER\n  TOLD\n  TOMORROW\n  TOO\n  TOTAL\n  TOTALLY\n  TOUCH\n  TRY\n  TWICE\n  UNDER\n  UNDERSTAND\n  UNDERSTOOD\n  UNTIL\n  UP\n  US\n  USED\n  USING\n  USUALLY\n  VARIOUS\n  VERY\n  WANT\n  WANTED\n  WANTS\n  WAS\n  WATCH\n  WAY\n  WAYS\n  WE\n  WE'RE\n  WELL\n  WENT\n  WERE\n  WHAT\n  WHAT'S\n  WHATEVER\n  WHATS\n  WHEN\n  WHERE\n  WHERE'S\n  WHICH\n  WHILE\n  WHILST\n  WHO\n  WHO'S\n  WHOM\n  WILL\n  WISH\n  WITH\n  WITHIN\n  WONDER\n  WONDERFUL\n  WORSE\n  WORST\n  WOULD\n  WRONG\n  YESTERDAY\n  YET\n".each_line.to_a.map(&:strip)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeMegaHAL

Create a new MegaHAL instance, loading the :default personality.



11
12
13
14
15
16
17
18
19
# File 'lib/megahal/megahal.rb', line 11

def initialize
  @learning = true
  @seed = Sooth::Predictor.new(0)
  @fore = Sooth::Predictor.new(0)
  @back = Sooth::Predictor.new(0)
  @case = Sooth::Predictor.new(0)
  @punc = Sooth::Predictor.new(0)
  become(:default)
end

Instance Attribute Details

#learningObject

Returns the value of attribute learning.



8
9
10
# File 'lib/megahal/megahal.rb', line 8

def learning
  @learning
end

Class Method Details

.add_personality(name, data) ⇒ Object



38
39
40
41
42
# File 'lib/megahal/megahal.rb', line 38

def self.add_personality(name, data)
  @@personalities ||= {}
  @@personalities[name.to_sym] = data.each_line.to_a
  nil
end

.extract(words) ⇒ Object

This takes an array of capitalised (normalised) words, and returns an array of keywords (which simply remove banned words, and switch some words with their antonyms). This exists purely to emulate the original MegaHAL. It would be better if keywords were learned by observing question-answer pairs.



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/megahal/keyword.rb', line 6

def self.extract(words)
  return GREETING if words.nil?
  words
    .map do |word|
      if word =~ /^[0-9]/
        nil
      elsif BANNED.include?(word)
        nil
      elsif SWAP.key?(word)
         SWAP[word]
      else
        word
      end
    end
    .compact
    .uniq
end

.listArray

Returns an array of MegaHAL personalities.



47
48
49
50
# File 'lib/megahal/megahal.rb', line 47

def self.list
  @@personalities ||= {}
  @@personalities.keys
end

Instance Method Details

#become(name = :default, bar = nil) ⇒ Object

Loads the specified personality. Will raise an exception if the personality parameter isn’t one of those returned by #list. Note that this will clear MegaHAL’s brain first.

Raises:

  • (ArgumentError)


58
59
60
61
62
63
# File 'lib/megahal/megahal.rb', line 58

def become(name=:default, bar = nil)
  raise ArgumentError, "no such personality" unless @@personalities.key?(name)
  clear
  bar.total = @@personalities[name].length unless bar.nil?
  _train(@@personalities[name], bar)
end

#clearObject

Wipe MegaHAL’s brain. Note that this wipes the personality too, allowing you to begin from a truly blank slate.



27
28
29
30
31
32
33
34
35
36
# File 'lib/megahal/megahal.rb', line 27

def clear
  @seed.clear    
  @fore.clear    
  @back.clear    
  @case.clear    
  @punc.clear    
  @dictionary = { "<error>" => 0, "<fence>" => 1, "<blank>" => 2 }
  @brain = {}
  nil
end

#inspectObject



21
22
23
# File 'lib/megahal/megahal.rb', line 21

def inspect
  to_s
end

#load(filename, bar = nil) ⇒ Object

Load a brain that has previously been saved.



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/megahal/megahal.rb', line 138

def load(filename, bar = nil)
  bar.total = 6 unless bar.nil?
  Zip::File.open(filename) do |zipfile|
    data = Marshal::load(zipfile.find_entry("dictionary").get_input_stream.read)
    raise "bad version" unless data[:version] == "MH11"
    @learning = data[:learning]
    @brain = data[:brain]
    @dictionary = data[:dictionary]
    bar.increment unless bar.nil?
    [:seed, :fore, :back, :case, :punc].each do |name|
      tmp = _get_tmp_filename(name)
      zipfile.find_entry(name.to_s).extract(tmp)      
      instance_variable_get("@#{name}").load(tmp)
      bar.increment unless bar.nil?
    end
  end
end

#reply(input, error = "...") ⇒ String

Generate a reply to the user’s input. If the learning attribute is set to true, MegaHAL will also learn from what the user said. Note that it takes MegaHAL about one second to generate about 500 replies.



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/megahal/megahal.rb', line 77

def reply(input, error="...")
  puncs, norms, words = _decompose(input ? input.strip : nil)

  keyword_symbols =
    MegaHAL.extract(norms)
        .map { |keyword| @dictionary[keyword] }
        .compact

  input_symbols = (norms || []).map { |norm| @dictionary[norm] }

  # create candidate utterances
  utterances = []
  9.times { utterances << _generate(keyword_symbols) }
  utterances << _generate([])
  utterances.delete_if { |utterance| utterance == input_symbols }
  utterances.compact!

  # select the best utterance, and handle _rewrite failure
  reply = nil
  while reply.nil? && utterances.length > 0
    break unless utterance = _select_utterance(utterances, keyword_symbols)
    reply = _rewrite(utterance)
    utterances.delete(utterance)
  end

  # learn from what the user said _after_ generating the reply
  _learn(puncs, norms, words) if @learning && norms

  return reply || error
end

#save(filename, bar = nil) ⇒ Object

Save MegaHAL’s brain to the specified binary file.



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/megahal/megahal.rb', line 112

def save(filename, bar = nil)
  bar.total = 6 unless bar.nil?
  Zip::File.open(filename, Zip::File::CREATE) do |zipfile|
    zipfile.get_output_stream("dictionary") do |file|
      data = {
        version: 'MH11',
        learning: @learning,
        brain: @brain,
        dictionary: @dictionary
      }
      file.write(Marshal::dump(data))
    end
    bar.increment unless bar.nil?
    [:seed, :fore, :back, :case, :punc].each do |name|
      tmp = _get_tmp_filename(name)
      instance_variable_get("@#{name}").save(tmp)
      zipfile.add(name, tmp)
      bar.increment unless bar.nil?
    end
  end
end

#train(filename, bar = nil) ⇒ Object

Train MegaHAL with the contents of the specified file, which should be plain text with one sentence per line. Note that it takes MegaHAL about one second to process about 500 lines, so large files may cause the process to block for a while. Lines that are too long will be skipped.



163
164
165
166
167
# File 'lib/megahal/megahal.rb', line 163

def train(filename, bar = nil)
  lines = File.read(filename).each_line.to_a
  bar.total = lines.length unless bar.nil?
  _train(lines, bar)
end