|
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 Payments Humor Music |
Support classes (rule and ruleset) for the learning classifier system program in Borland C++ for MSDOSThis page lists the C++ code for the Rule and RuleSet classes in the Classifier System project, a John Holland-style learning classifier system as described in the book Complexity, by M. Mitchell Waldrop. The project is more fully described on its home page (see link above). These Rule and RuleSet classes proved to be versatile, and they were generalized and used in my WTalk natural language processing chatbot project. |
/* rule.h 1-25-99
This file is part of the CLASSIFY project.
Copyright (C)1995-99 Steven Whitney.
Published under GNU GPL (General Public License) Version 3, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
*/
#ifndef __RULE_H
#define __RULE_H
#include <classlib\arrays.h>
#pragma hdrstop
#include "c:\bcs\my.h"
#include "bboard.h"
#define DONTCARE '#' // other characters may be better visually
////////////////////////////////////////////////////////////////////////////
// a rule is what searches bulletin board for its stimulus,
// and posts response if found.
class Rule
{
public:
Rule(int newbid = 99, string stim = "", string resp = ""); // 99 arbitrary, is < 100.
Rule(const Rule&); // copy constructor
Rule(Rule*, Rule*, int, int); // mating constructor
BOOL operator == (const Rule& other) const;
BOOL operator < (const Rule& other) const;
friend ostream& operator << (ostream& os, const Rule&); // read
friend istream& operator >> (istream& is, Rule&); // write
// functions
BOOL SameAs(const Rule& other) const; // an alternative to operator ==
// #error not yet written
double SimilarityTo(const Rule& other);
BOOL searchboard(TArrayAsVector<Bulletinboardentry>&); // search for stimulus
void paysource(int denominator); // pay source 1/denominator of its bid value
void AdjustBid(int amount); // receive feedback amount w/o overflowing or < 0
void SupportBid(int amount); // price support: bring bid up to at least amount
// variables
string stimulus; // in expanded version, these will be TArray<string>
string response;
int bid; // strength rating. must be int. see complex.doc.
// #error also use isabidder in locations where source is now used as a flag
BOOL isabidder; // is eligible in next lottery
BOOL wasawinner; // whether it was a winner in the last lottery
Rule* source; // the rule that posted this rule's "stimulus" on the board, OR
// 0=stimulus not on board. *self=sensor or self posted it.
// an alternative might be to always keep it as "this"
// unless it has a real source, so it's never zero.
BOOL locked; // if locked, can't be deleted: a ptr to it exists somewhere,
// posted on bulletin board or as another rule's ->source
// later, could be int: 0=not locked, others code reason for locking
protected:
void randomfill(string&, BOOL); // fill string with chars #,1,0
void mutate(); // mutate stimulus/response string(s)
};
//////////////////////////////////////////////////////////////////////////////
#endif // RULE.H
/* rule.cpp 1-25-99
This file is part of the CLASSIFY project.
Copyright (C)1995-99 Steven Whitney.
Published under GNU GPL (General Public License) Version 3, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
*/
#include "rule.h"
//////////////////////////////////////////////////////////////////////////////
//--------------------------------------------------------------------------
// constructor.
// When loading from disk, this constructor is used, then >> overwrites some members.
//
// Can optionally generate a custom-made Rule to respond to a given stimulus,
// and optionally to use a provided response. If you derive the response from
// parents, you must do it in RuleSet, since Rule (here) has no knowledge of
// other rules in the RuleSet. If you don't specify a response, this generates
// a random one.
Rule::Rule(int newbid, string stim, string resp)
{
isabidder = FALSE;
wasawinner = FALSE;
source = 0;
bid = newbid;
locked = FALSE;
if(stim.length()) // if stimulus was specified, use it.
stimulus = stim;
else
randomfill(stimulus,TRUE); // else random stimulus, with don't cares
if(resp.length()) // if response was specified, use it.
response = resp;
else
randomfill(response,FALSE); // random response, no don't cares
}
//--------------------------------------------------------------------------
// crossover mating constructor, mate 2 existing Rules to create the new one
// this is a sort of peculiar type of copy constructor that requires 2 sources
// original version, uses crossover. the stimulus and response strings are
// concatenated, and a cut point randomly selected in the 16 character strings.
// the new rule receives up-to-cutpoint chars from one parent, the rest from
// the other parent.
// stimulus response
// -------- --------
// e.g. #00 #000# 00000000
// and #1# 1111# 11111111, cut at 3 would produce children:
// -----------------
// #00 1111# 11111111
// or #1# #000# 00000000. (in book, both offspring are produced. see complex.doc)
/*
Rule::Rule(Rule* first, Rule* second, int newbid, int mutationrate)
{
isabidder = FALSE;
wasawinner = FALSE;
source = 0;
bid = newbid;
locked = FALSE;
if(!first || !second) // one or both pointers is null
{
if(!first && !second) // both null. random values at least
{ // guarantee a valid rule
randomfill(stimulus,TRUE); // random stimulus, with don't cares
randomfill(response,FALSE); // random response, no don't cares
}
else // only one is null. use the valid one as the only parent
{
Rule* parent = (first ? first : second);
stimulus = parent->stimulus;
response = parent->response;
}
}
else // both parents valid
{
// eliminate bias that can be introduced by the Classifier selection process:
// first is likely to be one of the strongest rules, since it
// won an "open to all" lottery. if first was the only strong rule in a field of
// very weak ones, second may only have been chosen because first couldn't
// mate with itself. Since the starting sequence always comes from
// first, it is more likely to have come from the stronger rule,
// when in fact its trailing sequence may have been the reason for its strength.
// The rules should have an equal chance of providing either portion.
if(random(2))
swap(first,second);
// concatenate stimulus-response of each
string firstcat = first->stimulus + first->response;
string secondcat = second->stimulus + second->response;
// create the crossover string
// copy first (cutpoint) chars from first, the rest of chars from second
int cutpoint = random(15)+1; // 1-15, at least one char from each parent
string temp = firstcat.substr(0,cutpoint) + secondcat.substr(cutpoint);
// for the new rule, temp is broken into stim,resp
stimulus = temp.substr(0,8);
response = temp.substr(8);
}
if(!random(mutationrate)) // introduce a random mutation
mutate();
} // end mating constructor
*/
//--------------------------------------------------------------------------
// mating constructor, mate 2 Rules to create the new one
// this is a sort of peculiar type of copy constructor that requires 2 sources
// new version, selects each position from one parent or the other
// assumes that first & second have same length stimulus & same length responses
// much simpler than above version and no significant learning speed difference.
Rule::Rule(Rule* first, Rule* second, int newbid, int mutationrate)
{
isabidder = FALSE;
wasawinner = FALSE;
source = 0;
bid = newbid;
locked = FALSE;
if(!first || !second) // one or both pointers is null
{
if(!first && !second) // both null. random values at least
{ // guarantee a valid rule
randomfill(stimulus,TRUE); // random stimulus, with don't cares
randomfill(response,FALSE); // random response, no don't cares
}
else // only one is null. use the valid one as the only parent
{
Rule* parent = (first ? first : second);
stimulus = parent->stimulus;
response = parent->response;
}
}
else // both parents valid
{
int length = first->stimulus.length(); // create stimulus string
for(int i = 0 ; i < length ; i++)
stimulus += (random(2) ? first->stimulus[i] : second->stimulus[i]);
length = first->response.length(); // create response string
for(i = 0 ; i < length ; i++)
response += (random(2) ? first->response[i] : second->response[i]);
}
if(!random(mutationrate)) // introduce a random mutation
mutate();
}
//--------------------------------------------------------------------------
// normal copy constructor
Rule::Rule(const Rule& other)
{
stimulus = other.stimulus;
response = other.response;
bid = other.bid;
isabidder = other.isabidder;
wasawinner = other.wasawinner;
source = other.source;
locked = other.locked;
}
//--------------------------------------------------------------------------
// You MUST use &other == this, not compare members. See RuleSet::Kill().
// The exact rule at the given ptr must be Destroyed, not just an identical one.
// Otherwise, you might have deleted the wrong one's bulletin board postings,
// thus leaving wild ptrs lying around, and risking pgm crash.
BOOL Rule::operator == (const Rule& other) const
{
return(&other == this);
}
//--------------------------------------------------------------------------
// for sorted container or comparison by bid.
// The only use for a sorted container is for a routine that finds the 20 highest bid rules.
// (RuleSet.Array must not be sorted)
BOOL Rule::operator < (const Rule& other) const
{
return(bid < other.bid);
}
//----------------------------------------------------------------------------
// output a rule to any ostream.
ostream& operator << (ostream& os, const Rule& r)
{
os << r.stimulus << " " << r.response << " " << r.bid;
return(os);
}
//-----------------------------------------------------------------------
// load a rule from an istream.
istream& operator >> (istream& is, Rule& r)
{
r.isabidder = FALSE;
r.wasawinner = FALSE;
r.source = 0;
r.locked = FALSE;
return(is >> r.stimulus >> r.response >> r.bid);
}
//--------------------------------------------------------------------------
// alternative to operator ==, actually compares members.
// maybe should omit comparing some.
BOOL Rule::SameAs(const Rule& other) const
{
return( (stimulus == other.stimulus) &&
(response == other.response) &&
(bid == other.bid) &&
(isabidder == other.isabidder) &&
(wasawinner == other.wasawinner) &&
(source == other.source) &&
(locked == other.locked) );
} // SameAs
//--------------------------------------------------------------------------
// fill a string: 50%=# (don't care), 25%='0', 25%='1' or 50% each 0,1
// the average % of DONTCAREs could be another evolvable Classifier constant.
void Rule::randomfill(string& s, BOOL usedontcare)
{
s.remove(0); // empty the string
for(int i = 0 ; i < 8 ; i++) // number of chars in the string
if(usedontcare && random(2))
s += DONTCARE;
else
s += random(2) ? '1' : '0';
} // end randomfill
//--------------------------------------------------------------------------
// mutate the stimulus or response string
// the mutation char isn't necessarily different from the one already there,
// so the effective mutation rate is actually lower than MUTATIONRATE
// assumes stimulus and response are both 8-char strings
void Rule::mutate()
{
int loc = random(8); // 0 to 7, position in string to mutate
if(random(2)) // mutate stimulus
{
if(random(2)) // 50% chance of don't care in stimulus section
stimulus[loc] = DONTCARE;
else
stimulus[loc] = (random(2) ? '1' : '0'); // 25% each 1:0
}
else // mutate response
{
response[loc] = (random(2) ? '1' : '0'); // response = 50% each 1:0
}
} // end mutate
//--------------------------------------------------------------------------
// if (stimulus) is on the board, records who the source was.
// if the source was a sensor posting (ZERO), ->source is set to point at itself.
// this makes it nonzero for testing and to mark it as a bidder.
// #error now using isabidder. is there any reason not to just leave it zero?
// is it tested for non-zero anywhere that isabidder won't do as well?
//
// A rule can also be a self-responder: its response posting might trigger its
// stimulus next time around, in which case its ->source would be "this" because
// it really was the source, not because it is a sensor responder. Doesn't
// seem to be a problem; it is prevented from paying itself, and even if it did,
// it wouldn't matter. But it should be kept in mind, and there should not
// be any sensor-responder bonus.
//
// It should be the Rule's responsibility to interpret the message passed,
// to determine whether it triggers it. I.e. the Rules are solely
// responsible for understanding the format they use.
// returns TRUE if it became a bidder, FALSE if not.
BOOL Rule::searchboard(TArrayAsVector<Bulletinboardentry>& bb) // board to search
{
int bbcount = bb.GetItemsInContainer(); // search bulletin board...
for(int i = 0 ; i < bbcount ; i++)
if(strand(bb[i].msg,stimulus,DONTCARE)) // is stimulus there?
{
isabidder = TRUE; // an easier flag to test
source = bb[i].responsible ? bb[i].responsible : this;
return(TRUE); // quit searching, can only respond to 1 stimulus
}
return(FALSE);
} // end searchboard
//--------------------------------------------------------------------------
// pay source 1/denominator of its bid value, OR the most that source can
// accept without overflowing
// because of integer math, payment is zero when bid is too low.
// that's why denominator should remain an int, not a double, fraction.
void Rule::paysource(int denominator)
{
// can't pay sensor, and no point in paying self
if(!source || (source == this))
return;
// how much of proposed payment can source accept without overflowing?
// proposed max acceptable
int payment = min(bid / denominator, MAXINT - source->bid);
source->bid += payment; // pay our "supplier"
bid -= payment; // subtract it from our wallet
// #error you could zero out source here, since it's served its purpose.
} // paysource
//--------------------------------------------------------------------------
// receive + or - feedback payment, OR the largest amount it can accept
// without overflowing or going negative.
// this is NOT related to paysource. it's for receiving SUCCESSREWARD or
// other feedback payments from the environment.
void Rule::AdjustBid(int amount)
{
if(amount >= 0)
bid += min(amount,MAXINT - bid);
else
bid = max(bid - amount,1); // 1 is actually the minimum legal value
} // AdjustBid
//--------------------------------------------------------------------------
// price support: bring bid up to at least given amount
void Rule::SupportBid(int amount)
{
bid = max(bid,amount);
} // SupportBid
//--------------------------------------------------------------------------
// end class rule
//////////////////////////////////////////////////////////////////////////////
/* ruleset.h 1-17-99
This file is part of the CLASSIFY project.
Copyright (C)1995-99 Steven Whitney.
Published under GNU GPL (General Public License) Version 3, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
*/
#ifndef __RULESET_H
#define __RULESET_H
#include <alloc.h>
#include <classlib\arrays.h>
#pragma hdrstop
#include "c:\bcs\my.h"
#include "rule.h"
////////////////////////////////////////////////////////////////////////////
// a RuleSet performs all functions required to manage the rule set for a Classifier.
class RuleSet
{
public:
RuleSet();
RuleSet(const RuleSet&); // copy constructor
~RuleSet(); // destructor
BOOL operator == (const RuleSet& other) const;
friend ostream& operator << (ostream& os, const RuleSet&); // write
friend istream& operator >> (istream& is, RuleSet&); // read
// allow treating the set as though it were derived from its array
Rule * & operator [] (int loc) const; // weird, but matches TIArray[]
unsigned GetItemsInContainer();
int highestbid(); // return highest bid any Rule has
int rulestodisk();
int rulesfromdisk();
// fill Array with new set of starting rules
BOOL newruleset(int rulecount = 0, int rulelimit = 0,
int initialbid = 0, int mutationrate = 0);
// fill to RULECOUNT with children or random rules
int TopUp(BOOL children = TRUE, BOOL preferbidders = FALSE);
BOOL Add(const Rule&); // add a copy of a specififed rule to Array
BOOL Addchild(BOOL preferbidders = FALSE); // select parents & add a child rule to Array
BOOL AddCustom(const string& stim); // make rule to respond to situation
BOOL Sweep(int minbid = 100); // delete all rules with bid < minimum
Rule* lottery(BOOL biddersonly); // chance of winning proportional to bid
int PayAllWinners(int amount, int denominator = 0); // returns number of winners paid
int SupportAllWinners(int amount); // returns number of winners paid
int MarkBidders(TArrayAsVector<Bulletinboardentry>&); // let all rules searchboard
BOOL IsASource(const Rule* r); // is r a source for any other rule?
// in whatever combinations are useful.
// for now, don't set these automatically IN other functions. keep separate.
void ZeroAllFlags(); // reset wasawinner, isabidder, and source
void ZeroSources(); // reset all ->source to zero
void UnlockAll(); // unlock all rules individually
int PostNewMessages(TArrayAsVector<Bulletinboardentry>&); // all winners post msgs
// variables
// these settings remain in effect for each new RuleSet
// this may be useful later
// static enum dispmodes { text, graphics } displaymode; // report as text or graphics
// Don't use sorted: first rule encountered in Killrules() must be the oldest.
// indirect: rules must be individually accessible outside the container
// keep considering protected derivation from TIArray, and/or as TArray<Rule*>
TIArrayAsVector<Rule> Array; // the set of rules (or "classifiers")
long childcount; // total number of child rules that have been created
protected:
// capitalized = constants
int RULECOUNT; // starting number of rules in the system
int RULELIMIT; // maximum allowed number of rules
int INITIALBID; // bidding "currency" a rule starts out with
// absolute min=1: if no bid, it can't enter lottery!
// integer math: 1 - 1/2 = 1 - 0 = 1 allows preserving minimum
// a new rule only gets this many passes to get chosen in lottery
int MUTATIONRATE; // a random mutation every 1 in this many offspring
int countbidders(); // count number of current bidders
long sumbids(BOOL biddersonly); // TRUE=bidders only, FALSE=all rules
int Killrules(int dcount = 1); // delete given number of rules
};
//----------------------------------------------------------------------------
// end class RuleSet
//////////////////////////////////////////////////////////////////////////////
#endif // RULESET.H
/* ruleset.cpp 1-17-99
This file is part of the CLASSIFY project.
Copyright (C)1995-99 Steven Whitney.
Published under GNU GPL (General Public License) Version 3, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
Code for class RuleSet
*/
#include "ruleset.h"
////////////////////////////////////////////////////////////////////////////
// global data
////////////////////////////////////////////////////////////////////////////
extern constream scr; // console screen
////////////////////////////////////////////////////////////////////////////
// RuleSet
//----------------------------------------------------------------------------
// these static members must be defined here. and give them values so we know
// they all are at least initialized to something valid.
// enum RuleSet::dispmodes RuleSet::displaymode = RuleSet::text;
//--------------------------------------------------------------------------
// constructor for normal use.
RuleSet::RuleSet() : Array(30,0,30)
{
// the constants must start with at least meaningful values. You can override later.
newruleset(20,28,76,1); // these values were evolved in early classify runs
}
//--------------------------------------------------------------------------
// copy constructor
RuleSet::RuleSet(const RuleSet& other) : Array(30,0,30)
{
RULECOUNT = other.RULECOUNT; // get constants
RULELIMIT = other.RULELIMIT;
INITIALBID = other.INITIALBID;
MUTATIONRATE = other.MUTATIONRATE;
// #error try using CHECK() to verify valid values
// copy the other's rules
int count = other.Array.GetItemsInContainer(); // how many rules to copy
for(int i = 0 ; i < count ; i++) // copy all the other's rules
Add(*(other.Array[i]));
} // end copy constructor
//--------------------------------------------------------------------------
// destructor
RuleSet::~RuleSet()
{
Array.Flush(TShouldDelete::Delete);
}
//--------------------------------------------------------------------------
// set the various constants for the RuleSet, and use them to
// fill Array with the required number of starting random rules.
// an omitted or 0 parameter means use existing value.
// newruleset() just creates a new ruleset with same constants as the previous one.
// caller should check the return value to make sure it was done.
BOOL RuleSet::newruleset(int rulecount, int rulelimit, int initialbid, int mutationrate)
{
childcount = 0L; // any previous children are now irrelevant
if(rulecount) RULECOUNT = rulecount;
if(rulelimit) RULELIMIT = rulelimit;
if(initialbid) INITIALBID = initialbid;
if(mutationrate) MUTATIONRATE = mutationrate;
// #error try using CHECK() to verify valid values
// probably should Flush in its own function, so it can fail if any Rule is locked,
// because ptrs to existing rules could remain in the bb.
Array.Flush(TShouldDelete::Delete); // first empty array, if necessary
TopUp(FALSE,FALSE); // create and initialize all the random rules
return(TRUE);
} // end newruleset
//--------------------------------------------------------------------------
// a RuleSet is equal only to itself
BOOL RuleSet::operator == (const RuleSet& other) const
{
return(this == &other);
}
//--------------------------------------------------------------------------
// write constants to a stream
ostream& operator << (ostream& os, const RuleSet& c)
{
// os << c.RULECOUNT << " " << c.RULELIMIT << " " << c.INITIALBID << " " << c.MUTATIONRATE;
// os << endl;
// now write the individual rules
return(os);
}
//--------------------------------------------------------------------------
// get constants from a stream
istream& operator >> (istream& is, RuleSet& c)
{
// is >> c.RULECOUNT >> c.RULELIMIT >> c.INITIALBID >> c.MUTATIONRATE;
// c.RULECOUNT = min(c.RULECOUNT,c.RULELIMIT); // prevent error
// c already has a rule set, based on old values of the variables just read in,
// so delete & rebuild the rule set for the new values.
// implement the just-read values
// c.newruleset(c.RULECOUNT, c.RULELIMIT, c.INITIALBID, c.MUTATIONRATE);
// other members reset, just because in principle >> should set everything
// c.childcount = 0; // children of the previous rule set now irrelevant
return(is);
} // end >>
//--------------------------------------------------------------------------
// return Array[loc]
Rule * & RuleSet::operator [] (int loc) const
{
return(Array[loc]);
} // operator []
//--------------------------------------------------------------------------
// return Array.GetItemsInContainer()
unsigned RuleSet::GetItemsInContainer()
{
return(Array.GetItemsInContainer());
} // GetItemsInContainer
//--------------------------------------------------------------------------
// return highest bid any Rule has. maybe useful for scaling screen display or other.
int RuleSet::highestbid()
{
int highbid = 0;
for(int i = 0 ; i < Array.GetItemsInContainer() ; i++)
highbid = max(highbid,Array[i]->bid);
return(highbid);
} // highestbid
//--------------------------------------------------------------------------
// add a copy of a Rule to Array. Because the actual new rule is newed here,
// the parameter r should be an automatic variable, so it vanishes.
// Returns TRUE if rule was successfully added, FALSE if not.
// But since Array is expandable, the only reason it can fail is if out of memory,
// which will throw an exception and end pgm anyway.
BOOL RuleSet::Add(const Rule& r) // the rule to save
{
// can impose a numeric limit on rule count:
if(Array.GetItemsInContainer() >= RULELIMIT)
Killrules(1);
// keep a minimum amount of memory free. This one should work, used this way,
// but unfortunately DOS-specific.
if(farcoreleft() < 50000UL)
Killrules(1); // deleting 1 should free enough room for a new one
if(Array.Add(new Rule(r))) // copy constructor creates a newed COPY of r
{
childcount++; // note: initial rules now get counted as children
return(TRUE);
}
return(FALSE);
} // end Add
//--------------------------------------------------------------------------
// select 2 parents by lottery and add their child to Array.
// The book (Complexity) has 2 strong rules mate. Here, parents are chosen by lottery.
// strong rules are most often selected, but a weak rule has a chance.
// It can either select the parents from all the rules, or give preference to
// bidders. If there aren't enough bidders, it will use any parent. Theory
// is that a child of a currently active parent is more likely to be useful.
// A rule has 2 parts, its stimulus and response. It is rewarded for having a
// good response by its bid being high, and therefore having a higher chance of
// being chosen in the lottery. This adds an extra reward for also having a
// currently-useful stimulus by preferring them as parents.
// The addition produced learn times 200 times faster.
// returns TRUE if a new child was created, FALSE if not
BOOL RuleSet::Addchild(BOOL preferbidders)
{
while(Array.GetItemsInContainer() < 2) // you must have at least 2 to mate
Add(Rule(INITIALBID));
BOOL firstabidder = FALSE;
BOOL secondabidder = FALSE;
if(preferbidders)
{
int biddercount = countbidders();
firstabidder = (biddercount > 0); // if any bidder, it can be first parent
secondabidder = (biddercount > 1); // if another, it can be the second
}
Rule *first = lottery(firstabidder); // select the pair of rules to mate
Rule *second = first;
while(second == first) // prevent first mating with itself
second = lottery(secondabidder);
// constructor does the mating and possible mutation
Rule newrule(first,second,INITIALBID,MUTATIONRATE);
return(Add(newrule)); // it should always succeed
} // end Addchild
//----------------------------------------------------------------------------
// make rule to respond to a particular situation (stimulus)
// And whose response is preferably based on the responses of
// existing rules whose stimuli are the most similar to the given one.
BOOL RuleSet::AddCustom(const string& stim)
{
// #error operational, but unfinished
// response should be built from the 2 most similar rules in the set, as
// parents, or the 1 most similar, or generated randomly as the last resort.
return(Add(Rule(INITIALBID,stim))); // it should always succeed
} // end AddCustom
//--------------------------------------------------------------------------
// fill to RULECOUNT (not to limit) with children or random rules.
// if children is FALSE, preferbidders is ignored.
// returns number added.
int RuleSet::TopUp(BOOL children, BOOL preferbidders)
{
int added = 0;
// Add can fail even if count < limit if farcoreleft limit exceeded.
while(Array.GetItemsInContainer() < RULECOUNT)
{
BOOL success;
if(children)
success = Addchild(preferbidders);
else
success = Add(Rule(INITIALBID));
if(success)
added++;
else
break; // if any Add failure, quit immediately
}
return(added);
} // end TopUp
//--------------------------------------------------------------------------
// remove weakest non-locked rule(s) from the array to make room for new one(s).
// returns number deleted
int RuleSet::Killrules(int dcount) // number of rules to eliminate
{
int deleted = 0;
for(int i = 0 ; i < dcount ; i++)
{
int rulecount = Array.GetItemsInContainer();
if(!rulecount) // nothing there to delete
return(deleted);
Rule* weakest = 0; // starts undefined
for(int j = 0 ; j < rulecount ; j++)
{
// start with first NON-LOCKED element as weakest
if(weakest == 0)
{
if(!Array[j]->locked && !IsASource(Array[j]))
weakest = Array[j];
continue;
}
// only eligible for deletion if it's weakest AND unlocked AND not another rule's source
if(!Array[j]->locked && (Array[j]->bid < weakest->bid) && !IsASource(Array[j]))
weakest = Array[j]; // simultaneously mark it for deletion.
}
// we found the first (oldest) non-locked rule in Array with the lowest bid.
// it had the longest to accumulate wealth, and didn't.
if(weakest)
{
Array.Destroy(weakest); // kill the rule
deleted++;
}
}
return(deleted);
} // end Killrules
//--------------------------------------------------------------------------
// delete all non-locked partial-solution rules (with bid < 100).
BOOL RuleSet::Sweep(int minbid)
{
for(int i = 0 ; i < Array.GetItemsInContainer() ; )
if(!Array[i]->locked && (Array[i]->bid < minbid) && !IsASource(Array[i]))
Array.Destroy(i); // kill it, and i gets reused for the next element,
else
i++; // else go to next
return(TRUE);
} // end Sweep
//--------------------------------------------------------------------------
// save entire rule set to disk
int RuleSet::rulestodisk()
{
ofstream outfile("rules.dat");
int count = Array.GetItemsInContainer();
for(int i = 0 ; i < count ; i++)
outfile << *(Array[i]) << endl;
return(count);
} // end rulestodisk
//--------------------------------------------------------------------------
// load rule set from disk
int RuleSet::rulesfromdisk()
{
Array.Flush(TShouldDelete::Delete); // empty it before filling
Rule r;
ifstream infile("rules.dat");
while(infile >> r) // also loads its old bid value
Add(r);
int count = Array.GetItemsInContainer();
if(!count)
graborts("File RULES.DAT corrupt or not found. Repair and rerun.");
return(count);
} // end rulesfromdisk
//--------------------------------------------------------------------------
// count the number of currently-marked bidders.
int RuleSet::countbidders()
{
int bidders = 0;
int N = Array.GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
if(Array[i]->isabidder)
bidders++;
return(bidders);
}
//--------------------------------------------------------------------------
// compute the total of all the rules's bid fields. Used by lottery().
// total won't exceed LONG_MAX: 32767 rules * 32767 each's bid is still less.
long RuleSet::sumbids(BOOL biddersonly) // TRUE=bidders only, FALSE=all rules
{
long bidtotal = 0L;
int N = Array.GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
if(Array[i]->isabidder || !biddersonly)
bidtotal += (long)(Array[i]->bid);
return(max(bidtotal,0L)); // just in case, prevent returning negative
} // end sumbids
//--------------------------------------------------------------------------
// lottery: create a pool into which each rule puts "bid" number of entries,
// so its chance of being chosen is proportional to its strength.
// only determines the lottery winner, so result can be used however needed.
// returns pointer to the rule that won the lottery, or 0 if no bidders
Rule* RuleSet::lottery(BOOL biddersonly) // TRUE=bidding, FALSE=mating
{
long bidtotal = sumbids(biddersonly); // sum of all ->bid fields
if(bidtotal <= 0L) // no point in a lottery if no bidders OR no rules
return(0); // (and a < 0 test to avoid catastrophe)
long r = lrandom(bidtotal); // random number from 0 to bidtotal-1
// run through rules to find which one "exhausts" the random number
// (it's more likely to happen while subtracting a large bid)
int N = Array.GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
{
if(Array[i]->bid <= 0) // convenient place for disaster test
graborts("ERROR: A bid <= 0");
// if it's a bidder (including a self-pointer), OR if we're allowing all rules
if(Array[i]->isabidder || !biddersonly)
{
r -= (long)(Array[i]->bid); // subtract the bid
if(r < 0L) // did it exhaust the random number?
{
// a possibility, but what if lottery was for mating only?
// Array[i]->wasawinner = TRUE;
return(Array[i]); // return pointer to winner
}
}
}
if(!biddersonly) // an all-rules lottery shouldn't fall through to here
graborts("ERROR: No lottery winner.");
return(0);
} // end lottery
//--------------------------------------------------------------------------
// pay all rules that were active when correct solution was found.
// if passback denominator is provided, rules also pay their sources now.
// returns number of winners paid
int RuleSet::PayAllWinners(int amount, int denominator)
{
int winners = 0;
int N = GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
if(Array[i]->wasawinner)
{
Array[i]->AdjustBid(amount);
// paying after receiving reward means source also gets its share of reward now.
// which speeds passback up by 1 pass.
if(denominator)
Array[i]->paysource(denominator);
winners++;
}
return(winners);
} // PayAllWinners
//--------------------------------------------------------------------------
// pay using price support
// returns number of winners paid
int RuleSet::SupportAllWinners(int amount)
{
int winners = 0;
int N = GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
if(Array[i]->wasawinner)
{
Array[i]->bid = max(Array[i]->bid,amount);
winners++;
}
return(winners);
} // SupportAllWinners
//--------------------------------------------------------------------------
// let all rules searchboard().
// returns number of bidders created.
int RuleSet::MarkBidders(TArrayAsVector<Bulletinboardentry>& bb)
{
int bidders = 0;
int N = GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
if(Array[i]->searchboard(bb))
bidders++;
return(bidders);
} // MarkBidders
//--------------------------------------------------------------------------
// is r a source for any other rule?
// this is useful in determining whether a rule can be deleted.
BOOL RuleSet::IsASource(const Rule* r)
{
int N = GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
if(Array[i]->source == r)
return(TRUE);
return(FALSE);
} // IsASource
//--------------------------------------------------------------------------
void RuleSet::ZeroAllFlags()
{
int N = GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
{
Array[i]->wasawinner = FALSE;
Array[i]->isabidder = FALSE;
Array[i]->source = 0;
}
} // ZeroAllFlags
//--------------------------------------------------------------------------
void RuleSet::ZeroSources()
{
int N = GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
Array[i]->source = 0;
} // ZeroSources
//--------------------------------------------------------------------------
void RuleSet::UnlockAll()
{
int N = GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
Array[i]->locked = FALSE;
} // UnlockAll
//--------------------------------------------------------------------------
// all winners post msgs
int RuleSet::PostNewMessages(TArrayAsVector<Bulletinboardentry>& bb)
{
int count = 0;
int N = GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
if(Array[i]->wasawinner)
{
bb.Add(Bulletinboardentry(Array[i]->response,Array[i])); // Add the new element
Array[i]->locked = TRUE; // prevent deletion while its posted
count++;
}
return(count);
} // PostNewMessages
//--------------------------------------------------------------------------
// end class RuleSet
////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
Copyright ©2011 Steven Whitney. Last modified Tue 05/24/2011 07:30:15 -0700. |
||