|
25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more... |
Home Projects Up Sitemap Search Blog Forum+Chat About Us Privacy Terms of Use Feedback FAQ Images Services Ads Donate Humor |
|
|
DIC.cpp for the WTalk chatbotDefines the word token class for the WTalk.cpp natural language processing chatbot project. The TOK class interacts with the Microsoft Access database WTalk.mdb using SQL via Windows Dynamic Data Exchange (DDE), retrieving its initial data from it when it is constructed, and at various times updating or inserting data into the database. |
|
/* dic.h 9-5-01
This is part of the WTalk project.
Copyright (C)1993-2001 Steven Whitney.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
Dictionary classes.
------
To do:
*/
#ifndef __DIC_H
#define __DIC_H
#include "c:\bcs\library\ddemlapp.h"
#include <classlib\arrays.h>
#include "c:\bcs\my.h"
#pragma hdrstop
//////////////////////////////////////////////////////////////////////////////
// unsure where this enum will eventually go. its values must match those in
// MDB, so they should be linked somehow.
//
// parts of speech - do not use zero or negative
// parts that user might enter must start at 1 and then be in sequence with no gaps.
// tried START = 0, but caused Illegal Word type in statmach.cpp.
// check whether the "nonzero" test in statmach is actually meaningful and needed.
enum
{
// all .DIC file entries use absolute numeric types: do NOT change the defs here!
UNASSIGNED = 0, // 0 also indicates illegal type, and delimits the list
NOUN, // 1 including pronouns
VERB, // 2
ADJ, // 3 including possessives that can follow "the"
ADV, // 4
DET, // 5 articles, and possessives that can't follow "the"
PREP, // 6
CCONJ, // 7 recheck word assignments to CONJ categories.
SCONJ, // 8 and I think state machine data files need revisions.
EXCL, // 9 exclamations and interjections
PUNCT, // 10 all non-TERM punctuation except
TERM, // 11 terminator: (.?;!:) not sure how to handle punct.
VBHELP, // 12 helper word in multi-word verb tenses: example: to
NADV // 13 Adverb that cannot modify a verb by itself, and usually
// cannot exist by itself; must modify an ADV or ADJ
};
/////////////////////////////////////////////////////////////////////////////
// A TOK is one word or punctuation mark in a sentence, with info on its usage.
// A number of its variables (e.g. TypeList) are maintained locally because a DDE lookup
// for every use is much too slow. However, these local variables only apply to THIS tok;
// any subsequent toks created get the latest info from MDB.
//
class TOK
{
public:
// "" prevents lookup time, "word" ensures all have length for debugging
TOK(string s = "");
TOK(const TOK& other); // copy
// comparison functions
BOOL operator == (const TOK& other) const; // exact match spelling and typenow (usage)
double Similarity(const TOK& other); // how similar they are, by various measures
double Similarity(const TOK* other) { return Similarity(*other); }
// haven't decided whether these should just output the strings, or what
friend ostream& operator << (ostream& os, const TOK&); // write
// functions
BOOL AnyUndefinedWords(); // whether any in MDB: fn not currently used
// could be static fn.
long LookupAlphaID(); // look up in MDB
uint LoadTypes();
int TypeCount() { return TypeList.length(); } // # of parts of speech it can have
BOOL HasType(uint part); // whether this word can be the given part of speech
BOOL OnlyHasType(uint part); // whether it can ONLY be the given part of speech
BOOL TypeIs(uint part) {return(typenow == part);} // whether it is now the given type
uint Type() {return typenow;}
uint TypeAfter(uint part);
uint nexttype(); // give word the next type in its list
void SetTypenow(uint newtype); // set to newtype, making it legal if necessary
operator string() const; // allows treating the TOK as its string
// variables
enum phrasetypes // grammatical phrase types
{
// 0 1 2 3 4 5 8
UNASSIGNED, NOUNPHR, VERBPHR, ADJPHR, ADVPHR, PREPPHR, SUBORD = 8
};
// type of info a phrase contains.
// eventually, identify & create file lists of key words that can indicate why and how.
// some words here (and in other lists) appear in more than one list.
// may require a more careful search of surrounding words to resolve ambiguities.
// why: because, since, as, for
// how: by (with a verb ending in "ing"), with (a "howprep")
enum infotypes
{
MISC, // 0 MISC OR UNASSIGNED OR UNKNOWN
WHO, // 1 SUBJECT
DIDWHAT, // 2 VERB
TOWHOM, // 3 DIR. OR INDIR. OBJECT
WHEN, // 4 WHEN
WHERE, // 5 WHERE
WHY, // 6 WHY
HOW, // 7 IN WHAT MANNER
COND, // 8 CONDITION (IF...)
ANSYES, // 9 A YES ANSWER, IN EFFECT
ANSNO, // 10 A NO ANSWER, IN EFFECT
ANSIND, // 11 INDETERMINATE (YOU ONLY KNOW IT WAS INTENDED AS AN ANSWER)
// TWO ADDL POSSIBILITIES FOR A TRAILING PHRASE
// BESIDES DIRECT OR INDIRECT OBJECT
EQUIV, // 12 EQUIVALENCY: [X] IS [NOUNPHRASE]
DESCR // 13 DESCRIPTION: [X] IS [ADJPHRASE]
};
string Alpha; // spelling, as taken from user sentence (not MDB)
string TypeList; // simple type list as the string's chars
int PhraseType; // phrase type that the token is part of
int InfoType; // type of information token contains: who, didwhat, etc.
long AlphaID; // the word's AlphaID in WTALK.MDB Phrases table.
long DefID; // easy access to the word's definition, synonyms, attribute lists,
// etc., as it's used in that location,
// which makes various analyses based on "context" possible.
// It should also help with parsing, because a word's usage usually is consistent
// throughout a conversation, so if you can confidently determine it for one occurrence,
// that's the safest assumption thereafter.
// (unrelated note: sense-based operational definition member(s) go into MDB, too)
// STATIC MEMBERS
static SDDEHandler* Dde; // a copy of the app's handler, for local use
// STATIC FUNCTIONS
static int tonumeric(string& type); // was in DEF
static string toalpha(int type);
static uint IntToWordtypes(int i);
static string phrasetoalpha(int type);
static string infotoalpha(int type);
protected:
uint typenow; // current part of speech assigned. you must set it with SetTypenow()
};
//----------------------------------------------------------------------------
// class TOK
//////////////////////////////////////////////////////////////////////////////
// an entry in a TOK similarity comparison table (for use in a TArray)
// the members are pointers to external, pre-existing TOKs so that after you
// have chosen the best match for a TOK, it is easy to delete from the array
// all the other potential pairings for that exact TOK (not TOKs that happen to
// have the same alpha spelling, etc.). That is, a FACT might contain multiple instances
// of the same word, possibly even with the same part of speech, and we need
// to be able to identify an exact TOK.
//
class TOKComparisonEntry
{
public:
TOKComparisonEntry(TOK* first = 0, TOK* second = 0); // constructor
TOKComparisonEntry(const TOKComparisonEntry&); // copy constructor
BOOL operator == (const TOKComparisonEntry& other) const; // equality
BOOL operator != (const TOKComparisonEntry& other) const; // inequality
BOOL operator < (const TOKComparisonEntry& other) const; // less than
// functions
BOOL RefersTo(const TOK* other) const; // whether either a or b matches other
// variables
TOK* a;
TOK* b;
double Similarity;
protected:
};
//----------------------------------------------------------------------------
// class TOKComparisonEntry
//////////////////////////////////////////////////////////////////////////////
#endif // dic.h
/* dic.cpp 8-25-01
This is part of the WTalk project.
Copyright (C)1993-2001 Steven Whitney.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
Dictionary classes.
*/
#include "c:\bcs\library\ddemlapp.h"
#pragma hdrstop
#include "dic.h"
/////////////////////////////////////////////////////////////////////////////
// class TOK
//----------------------------------------------------------------------------
// Define static members.
SDDEHandler* TOK::Dde = 0;
//---------------------------------------------------------------------------
// constructor
TOK::TOK(string s)
{
Alpha = s;
typenow = UNASSIGNED;
PhraseType = TOK::UNASSIGNED;
InfoType = MISC;
AlphaID = 0L;
DefID = 0L;
LookupAlphaID();
LoadTypes();
} // constructor
//----------------------------------------------------------------------------
// copy constructor
TOK::TOK(const TOK& other)
{
Alpha = other.Alpha;
AlphaID = other.AlphaID;
DefID = other.DefID;
typenow = other.typenow;
PhraseType = other.PhraseType;
InfoType = other.InfoType;
TypeList = other.TypeList;
} // copy constructor
//----------------------------------------------------------------------------
// equal if they refer to the same DicEntry node AND their types (meanings) are the same
BOOL TOK::operator == (const TOK& other) const
{
return((AlphaID == other.AlphaID) && (typenow == other.typenow));
// return(DefID == other.DefID); // new, later
}
//---------------------------------------------------------------------------
// write
ostream& operator << (ostream& os, const TOK& )
{
string s("Function operator << (ostream& os, const TOK& ) does nothing.");
logerror(s);
return(os << s);
}
//----------------------------------------------------------------------------
// measures how similar two TOKs are.
// currently uses only the (case-insensitive) alpha spellings for the comparison.
// returns a value between 0 (no similarity at all) and 1.0 (identical)
// Measure is the same regardless of which TOK is "other".
double TOK::Similarity(const TOK& other)
{
// #error could pre-test for whether typenow is the same, but that assumes that both TOKs
// are in already-parsed FACTs, and it assumes more accurate type assignment than
// is now possible.
return(similarity(Alpha,other.Alpha));
} // Similarity
//---------------------------------------------------------------------------
// this is used a LOT in fact.cpp. keep it fast.
TOK::operator string() const { return Alpha; }
//--------------------------------------------------------------------------
// tests by DDE query whether MDB Phrases table has words without definitions.
// currently unused fn, but keep for method.
BOOL TOK::AnyUndefinedWords()
{
string topic("SELECT DISTINCTROW Phrases.Alpha FROM Phrases "
"LEFT JOIN Defs ON Phrases.AlphaID = Defs.AlphaID "
"WHERE ((Defs.AlphaID Is Null));");
topic.prepend("WTALK;SQL ");
SDDEConv* chan1 = Dde->DDEInitiate("MSACCESS",topic);
BOOL anyleft = TRUE; // default in case of total failure
if(chan1)
{
anyleft = chan1->DDERequest("NextRow");
Dde->DDETerminate(chan1);
}
return(anyleft);
} //AnyUndefinedWords
//---------------------------------------------------------------------------
// look up and sets AlphaID from data in MDB, first adding it if necessary.
// returns AlphaID, as 0 if not found or DDE channel fails.
long TOK::LookupAlphaID()
{
AlphaID = 0L;
if(!Alpha.length())
return(AlphaID);
// moved here from PreProcess because it ensures ALL new words get added;
// much easier to enter all possible types for a word if it's the only one shown.
// do look it up first because this is called for EVERY TOK; usually will be found.
string searchtopic("WTALK;SQL SELECT DISTINCTROW Phrases.AlphaID FROM Phrases "
"WHERE (Phrases.Alpha=\"%s\");");
searchtopic.substring("%s") = Alpha; // string substitution! see all.cpp
SDDEConv* chan1 = Dde->DDEInitiate("MSACCESS",searchtopic);
if(!chan1)
{
logerror("topic rejected in LookupAlphaID");
return(AlphaID); // failure: returns 0
}
if(chan1->DDERequest("NextRow"))
{
fromstring(AlphaID,(string)(*chan1));
// update usecount, lastused fields for this phrase.
// the ability to update those fields for an individual DEF is still a long way off.
// this allows starting counting now. highest-use phrases should get highest priority
// for manual entry of some definitions and attributes, etc., for experimenting.
string t("[RUNSQL \"UPDATE DISTINCTROW Phrases "
"SET Phrases.UseCount = [Phrases].[UseCount]+1, Phrases.LastUsed = Now() "
"WHERE ((Phrases.AlphaID=%s));\"]");
t.substring("%s") = tostring(AlphaID);
chan1->DDEExecute("[SetWarnings 0]"); // required to prevent msgs, even for DDE
chan1->DDEExecute(t);
chan1->DDEExecute("[SetWarnings -1]"); // you want them ON for user data entry
Dde->DDETerminate(chan1);
return(AlphaID);
}
// REQUEST FAILED, SO ADD IT TO PHRASES. chan1 is still open for use
// NOTE the required double quotes around the string literal!
string addtopic = "[RUNSQL \"INSERT INTO Phrases([Alpha]) VALUES (\"\"%s\"\");\"]";
addtopic.substring("%s") = Alpha;
chan1->DDEExecute("[SetWarnings 0]"); // required to prevent msgs, even for DDE
chan1->DDEExecute(addtopic);
chan1->DDEExecute("[SetWarnings -1]"); // you want them ON for user data entry
// OPEN (NOT RUN) A PRE-DESIGNED QUERY ON ALL PHRASES WITHOUT DEFS
chan1->DDEExecute("[OpenQuery PhrasesWithoutDefs]");
chan1->DDEExecute("[Maximize]");
// #error ENTRY SHOULD ACTUALLY BE DONE ON A FORM, WITH BETTER INSTRUCTIONS THERE.
// clunky way to get a parent, but an unowned ::msgbox becomes its own task in task-switch,
// and is confusing.
TWindow* w = GetApplicationObject()->GetMainWindow();
w->MessageBox("Enter at least one Type (part of speech) for each word shown. "
"Use Alt+TAB or Ctrl+ESC to return here and close this box when you are done.",
"Please Task-Switch To MSAccess *NOW* To Enter Unknown Words",
MB_OK | MB_ICONINFORMATION);
chan1->DDEExecute("[Close 1,PhrasesWithoutDefs]"); // MUST close it
Dde->DDETerminate(chan1);
// #error needs work: put in loop?
// TRY AGAIN TO LOOK IT UP.
chan1 = Dde->DDEInitiate("MSACCESS",searchtopic);
if(chan1)
{
if(chan1->DDERequest("NextRow"))
fromstring(AlphaID,(string)*chan1);
else
logerror("Alpha not found on 2nd search in LookupAlphaID");
Dde->DDETerminate(chan1);
}
return(AlphaID);
} //LookupAlphaID
//----------------------------------------------------------------------------
// ok because the types list is no longer so permanent: it's only THIS tok.
// any later toks receive latest data.
// returns # of types loaded. auto-sets typenow to 1st listed type.
uint TOK::LoadTypes()
{
TypeList.remove(0);
typenow = UNASSIGNED;
if(AlphaID) // AlphaID of 0 = the flag of a 0-length string
{
// (see also SetTypenow())
// #error for each of these 2 sections (most common TYPE and most common DEF),
// you could first do a simpler query: use WHERE DateDiff("d",[LastUsed],Now()) < 1
// to find any entry used this same day; is a bit of trouble, but anything done
// here to ensure a correct default can save lots of trouble choosing later.
// unique types for Alphaid, by descending frequency of total encounters as each type
// so the first type in TypeList is its most common use.
string topic("WTALK;SQL "
"SELECT DISTINCTROW Defs.Type FROM Defs GROUP BY Defs.Type, Defs.AlphaID "
"HAVING ((Defs.AlphaID=%s)) ORDER BY Sum(Defs.UseCount) DESC , Defs.Type;");
topic.substring("%s") = tostring(AlphaID);
SDDEConv* chan1 = Dde->DDEInitiate("MSACCESS",topic);
if(chan1)
{
uint u;
while(chan1->DDERequest("NextRow"))
{
fromstring(u,(string)(*chan1));
TypeList += (uchar)u;
}
Dde->DDETerminate(chan1);
}
if(!TypeList.length()) // ensure all words have 1 type minimum
TypeList += (uchar)NOUN; // NOUN most common
SetTypenow(TypeAfter(0));
}
return(TypeCount());
} //LoadTypes
//----------------------------------------------------------------------------
// whether this word can be the given part of speech.
BOOL TOK::HasType(uint part)
{
for(int i = 0 ; i < TypeList.length(); i++)
if(TypeList[i] == part)
return(TRUE);
return(FALSE);
} //HasType
//----------------------------------------------------------------------------
// whether this word can ONLY be the given part of speech.
BOOL TOK::OnlyHasType(uint part) { return((TypeCount() == 1) && HasType(part)); }
//OnlyHasType
//----------------------------------------------------------------------------
// returns the next type in the list (if any), following the given type,
// or 0 if the end of the list was reached.
// if part == UNASSIGNED (0), it means you want the first listed type.
// Used by nexttype(), which is called by StateMachine.
// #error if that is its only use, maybe collapse into nexttype()?
uint TOK::TypeAfter(uint part)
{
if(!TypeList.length())
return(0);
if(!part)
return TypeList[0];
size_t u = TypeList.find(string((char)part));
if(u != NPOS)
if(++u < TypeList.length())
return(TypeList[u]);
return(0);
} // TypeAfter
//---------------------------------------------------------------------------
// give word the next type in its list
// returns the new type OR zero if the type wasn't changed
uint TOK::nexttype()
{
uint newtype = TypeAfter(typenow);
if(newtype) // only change typenow if next type was legal
SetTypenow(newtype); // otherwise it's left with the same type
return(newtype);
} // nexttype
//---------------------------------------------------------------------------
// set typenow to the given part of speech, making that type legal for it, if necessary,
void TOK::SetTypenow(uint newtype)
{
if(TypeIs(newtype))
return;
if((newtype != UNASSIGNED) && !HasType(newtype))
{
string s("Can the word \"%s\" have the type %s?");
s.substring("%s") = (string)(*this);
s.substring("%s") = tostring(newtype);
TWindow* w = GetApplicationObject()->GetMainWindow();
if(w && w->MessageBox(s.c_str(),"Auto-assign Word Type?",MB_ICONQUESTION|MB_YESNO)==IDYES)
{
SDDEConv* chan1 = Dde->DDEInitiate("MSACCESS","WTALK");
if(chan1)
{
s = "[RUNSQL \"INSERT INTO Defs ([AlphaID], [Type]) VALUES (%s, %s);\"]";
s.substring("%s") = tostring(AlphaID);
s.substring("%s") = tostring(newtype);
chan1->DDEExecute(s);
Dde->DDETerminate(chan1);
}
// even if sql failed, assign locally; don't call LoadTypes! may have come from there
TypeList += (uchar)newtype;
}
else
return;
}
typenow = newtype;
PhraseType = TOK::UNASSIGNED;
InfoType = MISC;
DefID = 0L;
if(typenow == UNASSIGNED)
return;
// GET INITIAL VALUES FROM THE MOST COMMON DEF OF THE TYPE JUST SET.
string topic = "WTALK;SQL SELECT DISTINCTROW Defs.DefID, Defs.InfoType, Defs.PhraseType "
"FROM Defs WHERE ((Defs.AlphaID=%s) AND (Defs.Type=%s)) "
"ORDER BY Defs.UseCount DESC , Defs.LastUsed DESC;";
topic.substring("%s") = tostring(AlphaID);
topic.substring("%s") = tostring(typenow);
SDDEConv* chan1 = Dde->DDEInitiate("MSACCESS",topic);
if(chan1)
{
// having these may make some of the WordLists unnecessary,
// except most words will have > 1 Def.
if(chan1->DDERequest("NextRow"))
{
fromstring(DefID,chan1->Fields[0]);
fromstring(InfoType,chan1->Fields[1]);
// fromstring(PhraseType,chan1->Fields[2]); // no: at Phrase() entry, all s/b UNASSIGNED
}
Dde->DDETerminate(chan1);
}
} //SetTypenow
//---------------------------------------------------------------------------
// #error change these to look up corresponding values in MDB
//---------------------------------------------------------------------------
// returns the numeric translation of given textual word type, or 0 if not found.
// allows entering data into state machine data files as text, e.g. noun,conj
// this translation and some other things would be easier if types were kept in
// an array of class wordtype.
int TOK::tonumeric(string& type)
{
type.to_upper();
if(type == "NOUN") return(NOUN);
if(type == "VERB") return(VERB);
if(type == "ADJ") return(ADJ);
if(type == "ADV") return(ADV);
if(type == "DET") return(DET);
if(type == "PREP") return(PREP);
if(type == "TERM") return(TERM);
if(type == "CCONJ") return(CCONJ);
if(type == "SCONJ") return(SCONJ);
if(type == "EXCL") return(EXCL);
if(type == "PUNCT") return(PUNCT);
if(type == "VBHELP") return(VBHELP);
if(type == "NADV") return(NADV);
return(0);
} // tonumeric
//---------------------------------------------------------------------------
// returns the appropriate text translation of given word type
// this is the complementary function to tonumeric, above
string TOK::toalpha(int type) // queried type
{
if(type == NOUN) { return("NOUN"); }
if(type == VERB) { return("VERB"); }
if(type == ADJ) { return("ADJ"); }
if(type == ADV) { return("ADV"); }
if(type == DET) { return("DET"); }
if(type == PREP) { return("PREP"); }
if(type == TERM) { return("TERM"); }
if(type == CCONJ) { return("CCONJ"); }
if(type == SCONJ) { return("SCONJ"); }
if(type == EXCL) { return("EXCL"); }
if(type == PUNCT) { return("PUNCT"); }
if(type == VBHELP) { return("VBHELP"); }
if(type == NADV) { return("NADV"); }
return("Illegal");
} //toalpha
//----------------------------------------------------------------------------
// #error if unused, delete
uint TOK::IntToWordtypes(int i)
{
switch(i)
{
case UNASSIGNED: return(UNASSIGNED);
case NOUN: return(NOUN);
case VERB: return(VERB);
case ADJ: return(ADJ);
case ADV: return(ADV);
case DET: return(DET);
case PREP: return(PREP);
case CCONJ: return(CCONJ);
case SCONJ: return(SCONJ);
case EXCL: return(EXCL);
case PUNCT: return(PUNCT);
case TERM: return(TERM);
case VBHELP: return(VBHELP);
case NADV: return(NADV);
default:
logerror("Illegal wordtype passed to DEF::IntToWordtypes(int i)");
return(UNASSIGNED);
}
} //IntToWordtypes
//---------------------------------------------------------------------------
// returns the appropriate text translation of the given numeric phrase type
string TOK::phrasetoalpha(int type) // queried type
{
if(type == NOUNPHR) { return("Noun"); }
if(type == VERBPHR) { return("Verb"); }
if(type == ADJPHR) { return("Adj"); }
if(type == ADVPHR) { return("Adv"); }
if(type == PREPPHR) { return("Prep"); }
if(type == SUBORD) { return("Subord"); }
// if(type == start) { return("start"); } // an error if it occurs
return("----");
}
//---------------------------------------------------------------------------
// returns the appropriate text translation of the given numeric InfoType
string TOK::infotoalpha(int type) // queried type
{
if(type == MISC) { return("Misc"); }
if(type == WHO) { return("Subject"); }
if(type == DIDWHAT) { return("DidWhat"); }
if(type == TOWHOM) { return("ToWhom"); }
if(type == WHEN) { return("When"); }
if(type == WHERE) { return("Where"); }
if(type == WHY) { return("Why"); }
if(type == HOW) { return("How"); }
if(type == COND) { return("Condition"); }
if(type == ANSYES) { return("YesAnswer"); }
if(type == ANSNO) { return("NoAnswer"); }
if(type == ANSIND) { return("IndAnswer"); }
if(type == EQUIV) { return("Equivalency"); }
if(type == DESCR) { return("Description"); }
return("----");
}
//----------------------------------------------------------------------------
// end class TOK
//////////////////////////////////////////////////////////////////////////////
// class TOKComparisonEntry
//----------------------------------------------------------------------------
// constructor
TOKComparisonEntry::TOKComparisonEntry(TOK* first, TOK* second)
{
a = first;
b = second;
if(first && second)
Similarity = first->Similarity(second);
else
Similarity = 0.;
} // constructor
//----------------------------------------------------------------------------
// copy constructor
TOKComparisonEntry::TOKComparisonEntry(const TOKComparisonEntry& other)
{
a = other.a;
b = other.b;
Similarity = other.Similarity;
} // copy constructor
//----------------------------------------------------------------------------
// equality
BOOL TOKComparisonEntry::operator == (const TOKComparisonEntry& other) const
{
return((a == other.a) && (b == other.b) && (Similarity == other.Similarity));
}
//----------------------------------------------------------------------------
// inequality: sometimes it seems that the compiler makes this conversion automatically
BOOL TOKComparisonEntry::operator != (const TOKComparisonEntry& other) const
{
return(!(other == *this));
}
//----------------------------------------------------------------------------
// less than: returns greater than, for a descending sort
BOOL TOKComparisonEntry::operator < (const TOKComparisonEntry& other) const
{
return(Similarity > other.Similarity);
}
//----------------------------------------------------------------------------
// whether either a or b matches other
BOOL TOKComparisonEntry::RefersTo(const TOK* other) const
{
return((a == other) || (b == other));
}
//----------------------------------------------------------------------------
// end class TOKComparisonEntry
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|