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.

Returns:

  • (Array)

    A list of symbols representing the available 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.

Parameters:

  • name (Symbol) (defaults to: :default)

    The personality to be loaded.

  • bar (ProgressBar) (defaults to: nil)

    An optional progress bar instance.

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.

Parameters:

  • filename (String)

    The brain file to be loaded.

  • bar (ProgressBar) (defaults to: nil)

    An optional progress bar instance.



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.

Parameters:

  • input (String)

    A string that represents the user’s input. If this is nil, MegaHAL will attempt to reply with a greeting, suitable for beginning a conversation.

  • error (String) (defaults to: "...")

    The default reply, which will be used when no suitable reply can be formed.

Returns:

  • (String)

    MegaHAL’s reply to the user’s input, or the error string if no reply could be formed.



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.

Parameters:

  • filename (String)

    The brain file to be saved.

  • bar (ProgressBar) (defaults to: nil)

    An optional progress bar instance.



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.

Parameters:

  • filename (String)

    The text file to be used for training.

  • bar (ProgressBar) (defaults to: nil)

    An optional progress bar instance.



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