Important alert: (current site time 7/15/2013 4:45:07 PM EDT)
 

article

__How to Build Eliza Chatterbot - A Program that can Chat with Humans

Email
Submitted on: 12/11/2002 11:00:16 AM
By: Amit Mathur  
Level: Intermediate
User Rating: By 20 Users
Compatibility: C, C++ (general), Microsoft Visual C++, Borland C++, UNIX C++
Views: 51892
(About the author)
 
     This article teaches you how you can create your own Chatterbot, a program that talks with human beings, just as we do. As you read this program, you will explore that this program, which falls under the domain of Artificial Intelligence, is nothing but manipulation of String and File Handling. This article aims at giving you a direction. The outcome is not a perfect program, but a minimal working skeleton. Rest you can do easily. There is practically NO LIMIT to how much input you can give to this program, because once you say that my Eliza is complete, I will find at least ONE question, that your Eliza will not be able to answer ! Hope you will enjoy this. And, Please Vote ;-)

This article has accompanying files

 
 
Terms of Agreement:   
By using this article, you agree to the following terms...   
  1. You may use this article in your own programs (and may compile it into a program and distribute it in compiled format for languages that allow it) freely and with no charge.
  2. You MAY NOT redistribute this article (for example to a web site) without written permission from the original author. Failure to do so is a violation of copyright laws.   
  3. You may link to this article from another website, but ONLY if it is not wrapped in a frame. 
  4. You will abide by any additional copyright restrictions which the author may have placed in the article or article's description.
				

How to Build Eliza Chatterbot
A Program that can Chat with Humans

Written by Amit Mathur on 10th to 12th Dec, 2002


What is Eliza ? 

Eliza is an AI Program that simulates the behavior of a therapist. The first program of this sort was developed in 1967 in MIT. Such programs, which interact with user in simple English language and can simulate a conversation are known as Chatterbot. 

A program like Eliza requires knowledge of three domains:

1.    Artificial Intelligence
2.    Expert System
3.    Natural Language Processing

Even though last two are sub-parts of the first one, they are emerging as  science in themselves.

Eliza can not, of course, think on its own. It has a repository or database of facts and rules, which are searched to give the best possible response.

Eliza works by matching process.  Very rarely an entire sentence is matched to give the response. 

The rules are indexed by keywords. Some rules require no keyword.

How does it work ? 

Ninety percent of what Eliza says is found in the associated Data File. This file acts as Knowledge Base for the complete system. 

The in-built responses comprise the Static Database of the system. These are the responses for the following cases: 

1.    When Eliza does not understand what the user is talking about.

2.    When the user repeats himself.

3.    When the user does not type anything and just keeps on pressing Enter.

4.    For the greeting statements. 

The following strategy is used to respond to a request: 

Step 1: Eliza finds out if the user has given any null input. If so, it takes the fact from the static database to respond. 

Step 2: There are some in built responses that Eliza can recognize readily. It finds the presence of any such sentence after fragmenting the user’s input and remembers the associated keyword. This keyword defines the Context of the talk. 

Step 3: If no in-built sentence frame work is found, then the Eliza searches for the specific keyword to define the context. If no context is found, it deliberately motivates the user to speak about a specific topic.  

Step 4: A response is chosen (at this time, randomly) from the database of available responses. 

Step 5: Any necessary transpositions are done. For example, consider the following conversation: 

SAM> I PLAN TO GO TO JAIPUR TOMORROW WITH MY WIFE.

ELIZA> AND WHAT HAPPENS IF YOU WON'T GO TO JAIPUR WITH YOUR WIFE ?
 

Here, the word My has to be transposed to YOUR.

Step 6: To simulate the human conversationalists, Eliza simulates Typing and does so slowly with making spelling mistakes and correcting them.
 

What about Coding ?

Now let us start the real coding portion. I am using Turbo C IDE 3.0 as this is the IDE that most Indian Students use.

Note that the complete source code is in the Zip file that accompanies this file. But my main stress is on approach and not on coding. The code,  which is written in just 90 minutes, is good as a working skeleton. 

Before going into the detailed coding aspect, let us first see the structure of a sample Data File. Eliza recognizes certain keywords. If these keywords are found in the user input, then corresponding to that, from a predefined set of responses, one is chosen and displayed.

A keyword is separated in the data file (called Dictionary) from the responses by @KWD@ token. This token indicates that the next line that follows is actually a keyword, not a response.


@KWD@
HELLO
HI, HOW ARE YOU
HELLO DEAR !
@KWD@
I WILL
YOU WILL DO SO. I BELIEVE IT TOO...
WILL YOU BE ABLE TO DO SO ?
@KWD@
YES
ARE YOU SURE ?
HOW CAN YOU BE SO SURE ?
YOU SEEM TO BE VERY OPTIMISTIC.
@KWD@
NO
YOU SEEM TO BE VERY PESSIMISTIC.
NEVER SAY NO...
@KWD@
COMPUTER
I KNOW HOW TO WORK ON COMPUTER.
YOU ARE CURRENTLY USING A COMPUTER. RIGHT ?

<< Add whatever you want in above format >>

Contents of file : Eliza.dat

For example, in response to 'Hello', from the above dictionary, Eliza will give one of the following responses:

  • HI, HOW ARE YOU

  • HELLO DEAR !

Once this thing is clear, let us now define the Data Structures that we will be using. We create two classes : 

  • progstr - This is used to store the user's input related information.

  • resp - This is used to store the information about the various responses.

Let me give the code first and then I will explain it.


class progstr
{
public:
char userip[MAX_USER_INPUT];
char keyword[30];
int keyfound;
int keyno;
int nullip;
    // constructor
    progstr()
    {
        keyno=-1;
        nullip=0;
        keyfound=0;
    }
}ip;

class resp
{
int tot_resp;
int last_resp;
char replys[MAX_RESP_NO][MAX_RESP_LEN];
char word[MAX_KWD_LEN];
public:
    // constructor
    resp()
    {
    tot_resp=0;
    last_resp=-1;
    }

    int getcount()
    {
    return last_resp;
    }

    void addword(char str[MAX_KWD_LEN])
    {
    strcpy(word,str);
    }

    char * getword()
    {
    return word;
    }

    void addresp(char str[MAX_RESP_LEN])
    {
    strcpy(replys[++last_resp],str);
    }

    // defined later
    void display_resp(int num);
    void quit_display_resp(int num);

};

Basic Data Structures Involved

The character array userip is used to store the line typed by the user. Another array keyword is used to store the keyword, if any, found in that input. If a keyword is found, we make int keyfound to 1 else, it remains 0, as it is initialized to 0 in the Constructor. keyno stores the key number of the corresponding keyword.

nullip indicates whether the user has given any Null input ie, he is just pressing enter and doing nothing else.

Now let us come to the second class, resp. The first data member, tot_resp indicates the total number of responses for a given keyword. For example, for the keyword Hello, we have 2 responses (see Eliza.Dat). So, for that, tot_resp holds a value of 2. last_resp is used for the function processing and its use will be clear later on. 

The replies are actually stored in replys[MAX_RESP_NO][MAX_RESP_LEN] and the corresponding keyword is stored in the array word. 

Description of Functions in Class resp :

  • Constructor: 
    This is used to initialize the total number of responses to 0. Why last_resp is initialized to -1 will be clear when you look at the function add_resp.

  • int getcount():
    This function is used to get a count of how many responses are there for a given keyword. 

  • void addword(char str[MAX_KWD_LEN]):
    This is used to add a keyword.

  • char * getword():
    Used to return the keyword for a particular object of class resp.

  • void addresp(...):
    This is used to add a response corresponding to a given keyword.

  • void display_resp(int):
    This is used to display the response to the user corresponding to a given index number for the responses.  (actually it does more than that !).

  • void quit_display_resp(int):
    Difference between this function and above function is that it is used in the end when the user is quitting. So, it does not return the prompt to the user.

Let us now create a function that reads the contents of file Eliza.Dat in an array of objects of class resp, which we name keys. Since I have already explained both - the format of .Dat file and data structures, this function should by clear with little or no effort. The code is commented wherever necessary.

 


void read_from_file()
{
ifstream fin;
int index=-1;
fin.open("eliza.dat");
char line[MAX_RESP_LEN];
while(fin)
{
  fin.getline(line,MAX_RESP_LEN);
  char *ptr=NULL;
  ptr=strstr("@KWD@",line);
  if(strlen(line)<1)
   {
   break;
   }
  else if(ptr!=NULL)
   {
   // the next line is a keyword
   fin.getline(line,MAX_RESP_LEN);
   keys[++index].addword(line);
   }
  else
  {
  // it is a response
  keys[index].addresp(line);
  }
} // end of while
} // end of function

Read Contents of Eliza.Dat File

Now let us create a function for global initialization of the transposition words. This function is easy and I will not belabor it.


void initialize_global()
{
strcpy(wordin[0],"ARE");
strcpy(wordout[0],"AM");

strcpy(wordin[1],"AM");
strcpy(wordout[1],"ARE");

strcpy(wordin[2],"WERE");
strcpy(wordout[2],"WAS");

strcpy(wordin[3],"WAS");
strcpy(wordout[3],"WERE");

strcpy(wordin[4],"YOU");
strcpy(wordout[4],"ME");

strcpy(wordin[5]," I ");
strcpy(wordout[5],"YOU");

strcpy(wordin[6],"YOUR");
strcpy(wordout[6],"MY");

strcpy(wordin[7],"MY");
strcpy(wordout[7],"YOUR");

strcpy(wordin[8],"I'VE");
strcpy(wordout[8],"YOU'VE");

strcpy(wordin[9],"YOU'VE");
strcpy(wordout[9],"I'VE");

strcpy(wordin[10],"I'M");
strcpy(wordout[10],"YOU'RE");

strcpy(wordin[11],"YOU'RE");
strcpy(wordout[11],"I'M");

strcpy(wordin[12],"ME");
strcpy(wordout[12],"YOU");

strcpy(wordin[13],"YOU");
strcpy(wordout[13],"ME");
}

Basic Transformations


Let us now write a function for displaying the responses to the user. The first if statement in the for loop is used to make a deliberate typing error to make it appear more human like ;-). One character is randomly chosen for typing error. Special cases like New Line and Backspace are separately considered. (Think why ?). Now I introduce something new. A special character - *. Char * represents all of the text found AFTER the identified keyword, and before one of the following punctuation marks. 

For example, consider the user input

AMIT > CAN I GO TO INDORE TOMORROW
ELIZA > WHAT IF YOU DO NOT GO TO INDORE TOMORROW

The underlined portion is not stored in the dictionary, rather it is taken from the user input. In the file Eliza.Dat, we store this information as 

CAN I
WHAT IF YOU DO NOT *

Star (*) asks the program to simply copy whatever is typed after the keyword (here CAN I ) in the user input, as it is. I hope that now the function of * as a special keyword is clear. So, let us consider a more complicated case.

AMIT > CAN I GO TO INDORE TOMORROW WITH MY FRIEND
ELIZA > WHAT IF YOU DO NOT GO TO INDORE TOMORROW WITH MY FRIEND

Obviously this is not what we wanted. I am supposed to go with my friend, not one of Eliza ! So, we must perform some transformation also. When we think of transformation, the sentence gets divided in the following 3 sections:

  1. Text Before Transposition Word. (here, GO TO INDORE TOMORROW WITH )

  2. The Transposed keyword. (here, YOUR, in place of MY)

  3. Text After Transposition Keyword. (here, FRIEND ? )

The following code tackles the three cases in a very lucid manner.

void resp :: display_resp(int num)
{
cout<<"ELIZA > ";
for(int i=0;i<strlen(replys[num]);i++)
{
     // for deliberate typing errors 
     // (for simulating the human behavoir ;-)

     if(RanNum(6)==0)
     { 
     char c=RanNum(100);
     if(c=='\n' || c=='\b' || c==13)
          cout<<"w";
     else
          cout<<c;
     delay(RanNum(DELAY));
     // correcting the deliberate typing error
     cout<<"\b";
}


// * is used to write anything after the keyword
// as it is, but with some transformations like 
// converting MY to YOUR.
if(replys[num][i]=='*')
{
     char * s1=ip.userip+strlen(ip.keyword);
     short int flag=0;
     for(int m=0;m<TRANSPOSE;m++)
          {
          char * s2=wordin[m];
          char *ptr=NULL;
          ptr=strstr(s1,s2);

          if(ptr!=NULL)
          {
          // transposition word found in the
          // user input
          flag=1;

          // printing text before wordin[m]
          int times=ptr-s1;
          for(int i=0;i<times;i++)
          {
          delay(DELAY);
          cout<<ip.userip[strlen(ip.keyword)+i];
          }

          // printing the wordout
          cout<<wordout[m];
          // printing the left overs
          char c;
          c=*(ptr+strlen(wordin[m]));
          int t=0;
          while(c!='\0')
          {
          cout<<*(ptr+strlen(wordin[m])+t);
          t++;
          c=*(ptr+strlen(wordin[m])+t);
          }
          }
          } // end of for

     // if flag is still zero , this means no need for 
     // transposing any word.
     if(0==flag)
     {
          char c;
          c=*(s1+strlen(ip.keyword));
          int t=0;
          while(c!='\0')
          {
          cout<<*(s1+t);
          t++;
          c=*(s1+t);
     }
} // end of if
break;
}
else
{
cout<<replys[num][i];
delay(RanNum(DELAY));
}
} // end of for

// giving the prompt back to user
cout<<"\n"<<user<<" > ";
}

Function for displaying Eliza's Response

Finally we can work out a procedure for searching the keyword in the user's input.  MAX_KEY indicates the number of keywords in the DAT file. Here we are simply searching whether the keyword exists in the user input (anywhere).


void find_keyword()
{
int len=0;
int lenkey=0;
int key_no=0;
char teststr[50];
while((ip.keyfound==0) &&(key_no!=MAX_KEY))
{
// getting the length of the keyword
lenkey=strlen(keys[key_no].getword());

char *ptr=NULL;
ptr=strstr(ip.userip,keys[key_no].getword());
if (ptr!=NULL)
{
// keyword found !
ip.keyfound=1;
ip.keyno=key_no;
strcpy(ip.keyword,keys[key_no].getword());
break;
}
key_no++;
}
}

Simple Search Routine.


When all these routines are made, we integrate them in the main function. For complete source code, please download the accompanying Zip file with the article.


Future Prospects:
 

Like other AI programs, this program also has immense possibilities of improvement.

The following are the improvements, which can be made in it. 

  • Learning by Time or Experience can be Implemented.

  • All the previous talking can be stored in a array of strings, so that in case of user contradicting himself/herself, ELIZA can contradict him.

  • A database or a flat file, at least, can be used for the data and talk storage.

  • Sessions and User-Password Pairs can be established, so that, even after the completion of one session, next time, whenever the user enters his User Name and Password, ELIZA, will get all the relevant data and previous talks, related to the user, from the database itself.

  • A prospect for the cache Memory can be made, so as make the retrieval of data and information can be faster.  I had written a Research Paper on this topic one year ago, Intelligent Information Retriever - Inception of Artificial Intelligence in Search Engines. Follow the link to download it if you are interested.

  • Standard Search algorithm can be used for the faster or better search of the relevant result or answer.

  • Various Graphical signs and/or symbols can be incorporated to show emotions, making the conversations more lively and more realistic in nature.

Important Note About Sample Code:

Please note that the current functionality and features of this program are very limited and they are just for accompanying the article. If you want to make this program more intelligent, make entries in Eliza.Dat file.

You can also increase the string manipulation power of the program, like considering multiple lines from the user, etc. I had written this code in 1 1/2 hr. just to make it more easier for the readers of my article about what is happening.

HOW SMART YOU MAKE YOUR ELIZA DEPENDS ON HOW FAR YOU EXTEND THIS PROGRAM. THERE IS PRACTICALLY NO LIMIT ! 
THIS CODE IS THE MINIMAL WORKING SKELETON !!

Don't forget to read README.TXT before making conclusions about the program !

Complete Source Code:

Included in the accompanying Zip File.

winzip iconDownload article

Note: Due to the size or complexity of this submission, the author has submitted it as a .zip file to shorten your download time. Afterdownloading it, you will need a program like Winzip to decompress it.Virus note:All files are scanned once-a-day by Planet Source Code for viruses, but new viruses come out every day, so no prevention program can catch 100% of them. For your own safety, please:
  1. Re-scan downloaded files using your personal virus checker before using it.
  2. NEVER, EVER run compiled files (.exe's, .ocx's, .dll's etc.)--only run source code.

If you don't have a virus scanner, you can get one at many places on the net including:McAfee.com

 
Terms of Agreement:   
By using this article, you agree to the following terms...   
  1. You may use this article in your own programs (and may compile it into a program and distribute it in compiled format for languages that allow it) freely and with no charge.
  2. You MAY NOT redistribute this article (for example to a web site) without written permission from the original author. Failure to do so is a violation of copyright laws.   
  3. You may link to this article from another website, but ONLY if it is not wrapped in a frame. 
  4. You will abide by any additional copyright restrictions which the author may have placed in the article or article's description.

Report Bad Submission
Use this form to tell us if this entry should be deleted (i.e contains no code, is a virus, etc.).
This submission should be removed because:

Your Vote

What do you think of this article (in the Intermediate category)?
(The article with your highest vote will win this month's coding contest!)
Excellent  Good  Average  Below Average  Poor (See voting log ...)
 

Other User Comments
12/11/2002 8:20:00 PMspeedy5

seems cool except your cpp file will not compile under c++.net (under a normal win32 console project). using turbo has different functions for dos. wherex(), wherey(), gotoxy(), delay(), clrscr() do not exist in the headers u've imported. try to also use the new iostream: #include ... using namespace std; and you got a few warnings...

anyways, it looks cool!
(If this comment was disrespectful, please report it.)

 
12/12/2002 12:35:58 AMMike Nolan

Very interesting and well written. I learned a lot from this. Keep up the good work!
(If this comment was disrespectful, please report it.)

 
12/13/2002 8:01:58 AMBabyBear264

can this be done in java? Do you know
(If this comment was disrespectful, please report it.)

 
12/13/2002 8:30:21 AMOphielle

It rocks nice example
(If this comment was disrespectful, please report it.)

 
12/14/2002 9:43:12 PMvijayalayanmohan

can you do this in c?

(If this comment was disrespectful, please report it.)

 
2/8/2003 6:25:34 AM

vijayalayanmohan: Why don't u port it to c for yourself? It's quite easy to change I/O commands ;o)

(If this comment was disrespectful, please report it.)

 
2/8/2003 7:12:21 PM

I'm same as speedy5. How do you get those functions to work?
(If this comment was disrespectful, please report it.)

 
2/12/2003 6:10:59 PMbucky23

I am having a problem. My compiler cannot find these functions:
delay(DELAY);
wherex();
wherey();
gotoxy();
randomize();
cl rscr();
what
header files do I need to include?
(If this comment was disrespectful, please report it.)

 
2/22/2003 12:48:23 PM

I'm using MSVC++, and it can't find the functions delay(), wherex(), wherey(), gotoxy(), randomize() and clrscr() either. Plz give the code for these functions!
(If this comment was disrespectful, please report it.)

 
2/22/2003 1:17:23 PM

if you include windows.h, you can exchange delay(int) for sleep(int)
(If this comment was disrespectful, please report it.)

 
2/22/2003 5:17:39 PM

if you have msoftcon.cpp & msoftcon.h you can exchange most annoying functions. One way to get these files is to download the C++ source code game "Arena Evolution". you can remove the single calling of wherex(), since x is changed to 40 shortly after anyway. I have not found out how to replace the wherey() yet.
(If this comment was disrespectful, please report it.)

 
6/19/2003 5:22:56 AM

MAX_USER_INPUT and others like that, what are they about and what to they do? is there a substitute for them. btw a great bit of code!
(If this comment was disrespectful, please report it.)

 
7/6/2003 2:24:55 AMWolf McCloud

delay(DELAY); wherex(); wherey(); gotoxy(); randomize(); clrscr (); are borland c++ functions, you can find them on the web, or just ask someone to send them to you
(If this comment was disrespectful, please report it.)

 
7/6/2003 2:25:26 AMWolf McCloud

you can replace clrscr(); by system("cls");
(If this comment was disrespectful, please report it.)

 
7/6/2003 2:26:35 AMWolf McCloud

and randomize(); by srand(SEED_HERE); but randomizing in c++ is very different than from VB. borland c++ made a function to randomize like vb
(If this comment was disrespectful, please report it.)

 
8/21/2003 2:05:10 PM

If you are using MSVC++, here is my learnt source code for substituting the function gotoxy();

#include
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); // output handle
void gotorc(int x, int y)
{
COORD cursor;

cursor.X = x;
cursor.Y = y; // set cursor position
SetConsoleCursorPosition(hOutput, cursor);
}

However, I still can't find a console function in the MSDN library which return the cursor position to replace wherey();
(If this comment was disrespectful, please report it.)

 
4/21/2004 4:27:39 PMJohn

I'm trying to incorporate this into a program that connects and interacts with AOL's tic/toc clients (AOL Instant Messenger). I was going to do it in VB but you have changed my mind :-p Back to C++ I go! Who do I cite this to in the "helped me a freaking ton" section of the program?
(If this comment was disrespectful, please report it.)

 
8/6/2004 4:42:52 PM

Ok, so I've tried all recommendations for using this code in Visual C++ but am still having problems with the sleep function as well as the wherex(), wherey(), gotorc(), and some other little symbols in my for statements, which is very odd. Any suggestions?
(If this comment was disrespectful, please report it.)

 
10/15/2004 5:23:49 AM

VC++ this will help with the errors.
remember windows.h as well.
system("cls");

First things first, it is Sleep(int) and not sleep(int) case sensitive. The wherex()/wherey problem. create a temp variable that monitors where you are.
COORD Tcrd={-1,-1};
void gotoxy(int x, int y)
{
COORD crd = {x, y};
Tcrd=crd;

SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), crd);
}
and then manually move the y coord as the lines change which is not hard just takes a couple mins to get them all in the right spot.

(If this comment was disrespectful, please report it.)

 
1/6/2005 9:10:24 AM

i understand the code in main
display_line();
display_logo();
display_line();
but can you explain me why you use this function initialize_global();
(If this comment was disrespectful, please report it.)

 
1/6/2005 9:17:23 AM

hi my name is seshu age 22 (form indian)
i am lean c language
(If this comment was disrespectful, please report it.)

 
2/27/2005 10:28:22 PM

cant get it to compile? it says source code not compiled when i compile then run it
(If this comment was disrespectful, please report it.)

 
3/9/2005 8:50:39 AM

Hi, I'm ToxicBurn (a.k.a ColdSteel on some game servers!). I am an experienced Visual Basic programmer, and a new learner C/C++ programmer. Eliza is one hell a peice of work, I think its brilliant. I'm currently working on a vb chat server, and I'm thinking of incorperating a AI chatbot into it. Your work on Eliza is a brilliant example of AI in its raw form, a bot that can help with humans problems! Perhaps you could adapt it so it can act as an AI psychiatrist? Just thought lol! Good work
(If this comment was disrespectful, please report it.)

 
6/12/2005 1:35:02 PMGonzales Cenelia

great article!
(If this comment was disrespectful, please report it.)

 
6/21/2005 4:01:04 PMPeter Evers

Exellent! just where I'm looking for!
Thanks alot!
(If this comment was disrespectful, please report it.)

 
1/9/2006 6:01:09 PMnicole jackson

in my computer class i had an assignment to create my own chatbox and i was really not sure how to go about doing that until i read this article. it helped me a lot and i got an A for the assignment so thanks.
(If this comment was disrespectful, please report it.)

 
11/22/2006 4:56:57 PMnuclear_farner

plzz tell me what more a should add to the code i have added #include and now its just 1 error how can a fix that what else should a do? creat something with the same name?
(If this comment was disrespectful, please report it.)

 
3/15/2007 5:22:52 AMRyan

Hey eliza sorry 4 disturbing.. i cant run the code in visualC++ its say error line 10 #include..
Please help me
(If this comment was disrespectful, please report it.)

 
3/18/2008 9:38:53 PM

thank you! very good
(If this comment was disrespectful, please report it.)

 
5/10/2010 4:13:59 AMbigface007

First,thanks for the author,a wonderful example from which I learn a lot.
Second, there is a small bug in the program. In Class resp definition, the member function resp::getcount() should return (last_resp+1) instead of just last_resp, since the the actual number of response for a paticular keyword is one more than last_resp.
Also the variable tot_resp seem unnessasary.

(If this comment was disrespectful, please report it.)

 
5/10/2010 4:16:18 AMbigface007

some bug.
member function resp::getcount()
should return (last_resp +1)instead of just last_resp.
(If this comment was disrespectful, please report it.)

 
5/11/2010 4:20:50 AMALI_SAIF

VARY GOOD And make very intelejent progam
(If this comment was disrespectful, please report it.)

 
5/23/2012 8:25:33 PMRashmi

It was quite helping.
(If this comment was disrespectful, please report it.)

 

Add Your Feedback
Your feedback will be posted below and an email sent to the author. Please remember that the author was kind enough to share this with you, so any criticisms must be stated politely, or they will be deleted. (For feedback not related to this particular article, please click here instead.)
 

To post feedback, first please login.