Today we will implement a simple version of Eliza. Here is an example run:
Hi! I'm Eliza, what is your problem? > Hi How do you do... Please state your problem. > My name is Otfried I don't care about names. Go on. > I need to learn programming Why do you need to learn programming. > I am a CS professor How long have you been a cs professor. > Oh, for a long time. Can you elaborate on that? > It's because one should know programming to be a CS professor Does that reason explain anything else? > No Are you sure? > Absolutely! What does that suggest to you? > You do not seem to understand me. You're not really talking about me, are you? > Why don't you ask some meaningful questions? Why do you think I don't ask some meaningful questions. > Your questions seem a bit meaningless Why are you concerned about my questions seem a bit meaningless. > You make me angry Does anger satisfy you in some way? > Anger gets in the way of doing my job. What is your relationship with your boss like? > I feel stressed talking to my boss. Do you enjoy feeling stressed talking to your boss. > I am afraid of my boss. Do you believe it is normal to be afraid of your boss. > He appears in my nightmares. What does that dream suggest to you? > That I am scared. Do you enjoy being scared. > Can you ask some other questions? You want me to be able to ask some other questions. > I have friends who talk like you. Do your friends pick on you? > Why do you ask that? Oh, I ask that. > I do not enjoy this conversation. Why don't you enjoy this conversation. > Do you remember when you first learnt programming? You're not really talking about me, are you? > Shut up!
Don't panic! You will be surprised how simple the program really is.
Eliza is based around a table of responses. These responses are selected based on a keyword found in the user input. I have made a file replies.txt that contains these replies.
Let's have a look at a few lines from replies.txt:
hi|hello: How do you do... Please state your problem. name: Names don't interest me. I don't care about names. Go on. family|mother|father|sister|brother|husband|wife: Tell me more about your family. How do you get along with your family? i am|im: Did you come to me because you are* How long have you been* Do you believe it is normal to be* Do you enjoy being* i feel: Tell me more about such feelings. Do you often feel* Do you enjoy feeling* Why do you feel that way? : Say, do you have any psychological problems? I'm not sure I understand you fully. Can you elaborate on that?
Spaces at the beginning and end of each line are meaningless. A line that ends with a colon defines one or more keywords, separated by characters. All remaining lines are the possible replies.
So when I typed "Hi", Eliza detected the keyword "hi", and replied with her only possible response for this keyword, namely "How do you do... Please state your problem."
In my next input, Eliza detected the keyword "name", and randomly picked one reply from the two possibilities.
Sometimes, for instance when I said "Oh, for a long time", Eliza cannot find any keyword. In this case, Eliza choses one of several possible replies for the empty keyword (indicated in the replies.txt file with a single colon on a line), in this case "Can you elaborate on that?".
So far this was quite easy. But to make the conversation seem more intelligent, Eliza sometimes reuses part of the input. For instance, when I said "I am a CS professor", Eliza recognizes the keyword "i am", and choses the reply "How long have you been*". The * symbol at the end of this reply string means to quote part of the input, namely all the words after the keyword. So Eliza creates the reply "How long have you been a CS professor."
To make sense, the part of the input string that is begin reused has to be modified. For instance, when I said "I feel stressed talking to my boss", Eliza detects the keyword "i feel", and picks the reply "Do you enjoy feeling*". This would lead to the reply "Do you enjoy feeling stressed talking to my boss"—but that would be wrong, because it is not Eliza's boss, but mine, that we were discussing. So Eliza needs to "conjugate" the user input, replacing the word "my" by "your". Here are a few more examples:
> I am surprised that you can understand me. How long have you been surprised that I can understand you. > I need your advice on my situation. What about your own advice on your situation. > i do not know how to be myself. Do you wish to be able to know how to be yourself. > i am fond of my interest in your software. Why do you like your interest in my software. > i think that you are not interested in me. Does it please you to believe that I am not interested in you. > i think i am in love Did you come to me because you are in love.
We first need a function readReplies that reads the replies.txt file. This function should return two things:
Next, we need a function sanitize(s: String): String that preprocesses the user input. It should make all letters lower-case, remove all symbols except for the letters 'a'...'z', and make sure that consecutive words are separated by a single space only. It should also add a space at the front and the end of the string:
>>> sanitize("Well, isn't this interesting?? Hi there! ") well isnt this interesting hi there
Next, a function findKeyword(s: String, kwds: List<String>): String that finds the first keyword from the list that is contained in the string s. Note that we want keywords to match as words only: We don't want "im" to match in the string "he is impolite", so we add spaces around the keywords when we check the string. If no keyword from the list appears in the string, then return the empty string "".
Now it's time to build the main loop. Eliza first prints an introduction, and then enters an infinite loop where she reads a line from the user. The line is first preprocessed using the function sanitize. If the user typed "shut up", then Eliza terminates. If the user repeated the same input line, then Eliza only says "Please do not repeat yourself!". Otherwise, we use findKeyword to determine the keyword for the input line, look up the possible replies in the map, and chose a random reply. If the reply does not end with *, then it is simply printed, and the loop continues.
If the reply ends with a star, then Eliza takes the input string, finds the location of the keyword, and splits everything in the input string after the keyword into a sequence of words. It then goes through the words one by one to build up the response, substituting words according to the following map:
val conjugations = mapOf("are" to "am", "were" to "was", "i" to "you", "im" to "you are", "my" to "your", "me" to "you", "ive" to "you've", "you" to "I", "your" to "my", "myself" to "yourself", "yourself" to "myself")
Write a Korean Eliza :-)