Class: MegaHAL
- Inherits:
-
Object
- Object
- MegaHAL
- 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 =
<<-EOS.each_line.to_a.map(&:strip) DISLIKE HE HER HERS HIM HIS I I'D I'LL I'M I'VE LIKE ME MINE MY MYSELF ONE SHE THREE TWO YOU YOU'D YOU'LL YOU'RE YOU'VE YOUR YOURS YOURSELF EOS
- BANNED =
<<-EOS.each_line.to_a.map(&:strip) A ABILITY ABLE ABOUT ABSOLUTE ABSOLUTELY ACROSS ACTUAL ACTUALLY AFTER AFTERNOON AGAIN AGAINST AGO AGREE ALL ALMOST ALONG ALREADY ALTHOUGH ALWAYS AM AN AND ANOTHER ANY ANYHOW ANYTHING ANYWAY ARE AREN'T AROUND AS AT AWAY BACK BAD BE BEEN BEFORE BEHIND BEING BELIEVE BELONG BEST BETTER BETWEEN BIG BIGGER BIGGEST BIT BOTH BUDDY BUT BY CALL CALLED CALLING CAME CAN CAN'T CANNOT CARE CARING CASE CATCH CAUGHT CERTAIN CERTAINLY CHANGE CLOSE CLOSER COME COMING COMMON CONSTANT CONSTANTLY COULD CURRENT DAY DAYS DERIVED DESCRIBE DESCRIBES DETERMINE DETERMINES DID DIDN'T DO DOES DOESN'T DOING DON'T DONE DOUBT DOWN EACH EARLIER EARLY ELSE ENJOY ESPECIALLY EVEN EVER EVERY EVERYBODY EVERYONE EVERYTHING FACT FAIR FAIRLY FAR FELLOW FEW FIND FINE FOR FORM FOUND FROM FULL FURTHER GAVE GET GETTING GIVE GIVEN GIVING GO GOING GONE GOOD GOT GOTTEN GREAT HAD HAS HASN'T HAVE HAVEN'T HAVING HELD HERE HIGH HOLD HOLDING HOW IF IN INDEED INSIDE INSTEAD INTO IS ISN'T IT IT'S ITS JUST KEEP KIND KNEW KNOW KNOWN LARGE LARGER LARGETS LAST LATE LATER LEAST LESS LET LET'S LEVEL LIKES LITTLE LONG LONGER LOOK LOOKED LOOKING LOOKS LOW MADE MAKE MAKING MANY MATE MAY MAYBE MEAN MEET MENTION MERE MIGHT MOMENT MORE MORNING MOST MOVE MUCH MUST NEAR NEARER NEVER NEXT NICE NOBODY NONE NOON NOONE NOT NOTE NOTHING NOW OBVIOUS OF OFF ON ONCE ONLY ONTO OPINION OR OTHER OUR OUT OVER OWN PART PARTICULAR PARTICULARLY PERHAPS PERSON PIECE PLACE PLEASANT PLEASE POPULAR PREFER PRETTY PUT QUITE REAL REALLY RECEIVE RECEIVED RECENT RECENTLY RELATED RESULT RESULTING RESULTS SAID SAME SAW SAY SAYING SEE SEEM SEEMED SEEMS SEEN SELDOM SENSE SET SEVERAL SHALL SHORT SHORTER SHOULD SHOW SHOWS SIMPLE SIMPLY SMALL SO SOME SOMEONE SOMETHING SOMETIME SOMETIMES SOMEWHERE SORT SORTS SPEND SPENT STILL STUFF SUCH SUGGEST SUGGESTION SUPPOSE SURE SURELY SURROUND SURROUNDS TAKE TAKEN TAKING TELL THAN THANK THANKS THAT THAT'S THATS THE THEIR THEM THEN THERE THEREFORE THESE THEY THING THINGS THIS THOSE THOUGH THOUGHTS THOUROUGHLY THROUGH TINY TO TODAY TOGETHER TOLD TOMORROW TOO TOTAL TOTALLY TOUCH TRY TWICE UNDER UNDERSTAND UNDERSTOOD UNTIL UP US USED USING USUALLY VARIOUS VERY WANT WANTED WANTS WAS WATCH WAY WAYS WE WE'RE WELL WENT WERE WHAT WHAT'S WHATEVER WHATS WHEN WHERE WHERE'S WHICH WHILE WHILST WHO WHO'S WHOM WILL WISH WITH WITHIN WONDER WONDERFUL WORSE WORST WOULD WRONG YESTERDAY YET EOS
Instance Attribute Summary collapse
-
#learning ⇒ Object
Returns the value of attribute learning.
Class Method Summary collapse
- .add_personality(name, data) ⇒ Object
-
.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).
-
.list ⇒ Array
Returns an array of MegaHAL personalities.
Instance Method Summary collapse
-
#become(name = :default, bar = nil) ⇒ Object
Loads the specified personality.
-
#clear ⇒ Object
Wipe MegaHAL’s brain.
-
#initialize ⇒ MegaHAL
constructor
Create a new MegaHAL instance, loading the :default personality.
- #inspect ⇒ Object
-
#load(filename, bar = nil) ⇒ Object
Load a brain that has previously been saved.
-
#reply(input, error = "...") ⇒ String
Generate a reply to the user’s input.
-
#save(filename, bar = nil) ⇒ Object
Save MegaHAL’s brain to the specified binary file.
-
#train(filename, bar = nil) ⇒ Object
Train MegaHAL with the contents of the specified file, which should be plain text with one sentence per line.
Constructor Details
#initialize ⇒ MegaHAL
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
#learning ⇒ Object
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 |
.list ⇒ Array
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.
58 59 60 61 62 63 |
# File 'lib/megahal/megahal.rb', line 58 def become(name=:default, = nil) raise ArgumentError, "no such personality" unless @@personalities.key?(name) clear .total = @@personalities[name].length unless .nil? _train(@@personalities[name], ) end |
#clear ⇒ Object
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 |
#inspect ⇒ Object
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, = nil) .total = 6 unless .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] .increment unless .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) .increment unless .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, = nil) .total = 6 unless .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 .increment unless .nil? [:seed, :fore, :back, :case, :punc].each do |name| tmp = _get_tmp_filename(name) instance_variable_get("@#{name}").save(tmp) zipfile.add(name, tmp) .increment unless .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, = nil) lines = File.read(filename).each_line.to_a .total = lines.length unless .nil? _train(lines, ) end |