Hi Fred, here is the commented version of SplitMoo See my "splitmoo lite" mail for details -- FvdP //---------------------------------------------------------------------- // POTM ENTRY: SplitMoo // NAME: Frederic van der Plancke // E-MAIL: , // build instructions: no need for, I hope ! //---------------------------------------------------------------------- #include #include #include // for isdigit() #include // for log() #define MAX_LENGTH 9 #ifdef __BORLANDC__ # define TEST # include "SplitMoo.h" # define SRAND srand(0) // STL headers and other things volatile int debug_ok = 1; // set to zero if you want to stop namespace SM { #elif defined(_MSC_VER) # define TEST # include "SplitMooMSC.h" volatile int debug_ok = 1; # define main sm_main # define SRAND srand(0) #else // __GNUC__ then ! # include # include #endif #ifndef TEST # define ASSERT(p) (void(0)) # define DBG(v,m) (void(0)) # define SRAND #endif //---------------------------------------------------------------------- const char* ver = "final"; //---------------------------------------------------------------------- #define RDINT(in) (getw(in)) #define WRINT(out, n) putw(n, out) //---------------------------------------------------------------------- #define BIT(i,x) ((x >> i) & 0x01) #define IFILE FILE* #define OFILE FILE* // IFILE= ifstream& and OFILE= ofstream&, or IFILE=FILE*=OFILE #define RDWORD(in, w) \ { for(int i=0; i < WordLength; ++i) w[i] = getc(in); } #define WRWORD(out, w) \ { for(int i=0; i < WordLength; ++i) putc(w[i], out); } void RDVECT(IFILE in, vector & v) { int sz = RDINT(in); v.resize(sz); for (int i = 0; i < v.size(); ++ i) v[i] = RDINT(in); } void WRVECT(OFILE out, vector & v) { WRINT(out, v.size()); for (int i = 0; i < v.size(); ++ i) WRINT(out, v[i]); } double RDDBL(IFILE in) { float d; char* dc = (char*) &d; for (int i = 0; i < sizeof(d); ++ i) dc[i] = getc(in); return d; } void WRDBL(OFILE out, float d) { char* dc = (char*) & d; for (int i = 0; i < sizeof(d); ++ i) putc(dc[i], out); } typedef int SET; // do not replace 'int' by 'unsigned': 'int &' must stay // synonymous to 'SET &', otherwise Solver::save(int &) will not // work with SET. (it will create a temporary for its argument: // nasty thing !) //---------------------------------------------------------------------- // GLOBAL CONSTANTS //---------------------------------------------------------------------- int WordLength = 0; int GuessNo = 0; //---------------------------------------------------------------------- // GENERAL METHODS //---------------------------------------------------------------------- int bitcount(int x) // counts the number of bits in x // fast when few bits are set // otherwise a table-driven approach would be faster... // ...and require yet more source code space ! { int count = 0; while (x) { x = x & (x - 1); ++ count; // (nice trick, don't you think ? The idea is not mine...) } return count; } void Translate(char* word) { int delta = (*word < 'A') ? +65 : -65; for (int i = 0; i < WordLength; ++ i) word[i] += delta; } //---------------------------------------------------------------------- // TIME CONTROL //---------------------------------------------------------------------- #ifndef TEST #include // for the TMS structure #define CLK_TCK 100 int mytime100() { int seconds; static struct tms TMS; times(&TMS); // add the elapsed system and user times return TMS.tms_stime + TMS.tms_utime; } #else static int clock0 = 0; int mytime100() { static volatile int tick = CLK_TCK; int diff = clock() - clock0; return diff / 10; } #endif void time_check(int t100, int skip, bool & stop) { if (mytime100() > t100) stop = true; } //---------------------------------------------------------------------- //---------------------------------------------------------------------- //T H E C O N S T R A I N T P R O P A G A T I O N M O D U L E //---------------------------------------------------------------------- //---------------------------------------------------------------------- // only used in ASSERTions inline bool set_inclusion(SET lower, SET upper) { return (lower & ~ upper) == 0; } // only used in ASSERTions inline bool is_singleton(int x) { return x != 0 && (x & (x-1)) == 0; } inline int singleton_value(int x) { //ASSERT(is_singleton(x)); int v = -1; while (x) { ++ v; x >>= 1; } return v; } //---------------------------------------------------------------------- class Solver; Solver* S = 0; class PpgNode { public: PpgNode() : _flags(0) {} virtual void propagate(int flags) = 0; //private: friend Solver; int _flags; }; struct AVPair { int* addr; int val; }; #define MAX_QUEUE_LEN 200 class Solver { public: Solver() : _frozen(0), _qEnd(_queue + MAX_QUEUE_LEN), _failed(false) { _qRead = _qWrite = _queue; } void trigger(PpgNode* v, int flags) { //ASSERT(flags); //ASSERT(!_failed); bool isnew = (v->_flags == 0); v->_flags |= flags; if (isnew) { *_qWrite++ = v; if (_qWrite == _qEnd) _qWrite = _queue; if (! _frozen) propagateNow(); } } void freeze() { ++ _frozen; } void unfreeze() { -- _frozen; if (! _frozen) propagateNow(); } void fail() { _failed = true; } bool ok() const { return ! _failed; } void save(int & x) { AVPair av; // = { &x, x }; // I dare not writing that ! av.addr = &x; av.val = x; _trail.push_back(av); } void undoPoint() { _undoPts.push_back(_trail.size()); } void undo() { int trailIndex = _undoPts.back(); _undoPts.pop_back(); //ASSERT(trailIndex <= _trail.size()); // undoing _backwards_: that's mandatory, because one // address can have several undo records; only the // first one must be effective, not the last one. for (int i = _trail.size() - 1; i >= trailIndex; -- i) *_trail[i].addr = _trail[i].val; _trail.resize(trailIndex); // clearing propagation while (_qRead != _qWrite) { (*_qRead++)->_flags = 0; if (_qRead == _qEnd) _qRead = _queue; } // _failed must be cleared _failed = false; } //private: void propagateNow() { //ASSERT(! _frozen); // _failed==true is allowed ++ _frozen; while (_qRead != _qWrite && ! _failed) { PpgNode* node = *_qRead++; if (_qRead == _qEnd) _qRead = _queue; int flags = node->_flags; // flags must always be cleared before propagating: // (in case the propagation retriggers the propagated node) node->_flags = 0; node->propagate(flags); } -- _frozen; } //private members: PpgNode* _queue[MAX_QUEUE_LEN]; PpgNode** _qEnd; // _queue + MAX_QUEUE_LEN PpgNode** _qRead; // from where to pop next node PpgNode** _qWrite; // where to push next node vector _trail; vector _undoPts; int _frozen; bool _failed; }; //---------------------------------------------------------------------- // CONSTRAINT PROPAGATION : THE ABSTRACT VARIABLE(S) CLASS(ES) //---------------------------------------------------------------------- class SetVar : public PpgNode { public: SetVar(int cardinal, int possible_ = -1, int sure_ = 0) { _mask = (1u << cardinal) - 1; sure = sure_ & _mask; possible = possible_ & _mask; sureCount = bitcount(sure); possibleCount = bitcount(possible); _checkSingleton(); } SetVar(IFILE is) { _mask = RDINT(is); possible = RDINT(is); sure = RDINT(is); possibleCount = RDINT(is); sureCount = RDINT(is); _value = RDINT(is); } void dump(OFILE out) { WRINT(out, _mask); WRINT(out, possible); WRINT(out, sure); WRINT(out, possibleCount); WRINT(out, sureCount); WRINT(out, _value); } void initPpg() // probably useless, but I won't take the chance { int delta = _mask & ~ (possible ^ sure); if (delta) S->trigger(this, delta); } void initPpg(int count) { initPpg(); if (S->ok() && count == possibleCount) include(possible); if (S->ok() && count == sureCount) remove(~ sure); } void propagate(int) {}; bool fwdCheck(SET dom); // tests inclusion and exclusion of each element in dom; // if it detects a contradiction (an element that must be both in // and out) it returns false and lets S in a failed state. // next method is private: void _checkSingleton() { //ASSERT(bitcount(sure) == sureCount); //ASSERT(bitcount(possible) == possibleCount); if (sureCount == possibleCount && possibleCount == 1) _value = singleton_value(sure); } void include(int set) { int delta = _mask & set & ~ sure; if (delta & ~ possible) S->fail(); if (! S->ok()) return; if (delta) { S->save(sure); S->save(sureCount); sure ^= delta; sureCount = bitcount(sure); _checkSingleton(); S->trigger(this, delta); } } void remove(int set) { if (_mask & set & sure) S->fail(); if (! S->ok()) return; //ASSERT(set_inclusion(_mask & set, ~ sure)); int delta = set & possible; if (delta) { S->save(possible); S->save(possibleCount); possible ^= delta; possibleCount = bitcount(possible); _checkSingleton(); S->trigger(this, delta); } } //--- methods to make this appear as an EnumVar: void setValue(int value) { include(1 << value); } void removeValue(int value) { remove(1 << value); } int getValue() const { //ASSERT(sure==possible && is_singleton(sure)); //ASSERT(singleton_value(sure) == _value); return _value; } void ppgCardinal(int count) { if (sureCount == count) remove(~ sure); else if (possibleCount == count) include(possible); if (sureCount > count || possibleCount < count) S->fail(); // add "return" if query } bool ppgSingleton() { ppgCardinal(1); return (sureCount == 1); } public: // should be protected with read-only accessors SET possible; SET sure; int possibleCount; int sureCount; //private: int _value; // defined only if set is bound to a singleton SET _mask; }; //---------------------------------------------------------------------- //---------------------------------------------------------------------- // VARIABLES AND CONSTRAINTS //---------------------------------------------------------------------- //---------------------------------------------------------------------- class LetterAtPos; SetVar** _theLAPs_; SetVar** theLetterAtPos; //LetterAtPos** theLetterAtPos; // theLetterAtPos[iPos] : enum var :== letter at position iPos // theLetterAtPos[-2 .. WordLength + 1] class PossibleLetters; PossibleLetters* theLetters; class LetterUnionCtr; LetterUnionCtr* unionCtr; class Guess; vector theGuesses; // linear constraint on the char. function of possible letters class CharLinCtr; vector theCharLinCtrs; //---------------------------------------------------------------------- // LINEAR CONSTRAINTS ON PRESENT LETTERS //---------------------------------------------------------------------- class CharLinCtr { public: CharLinCtr(const vector & letters, const vector & count, int sum) : _letters(letters), _coeffs(count), _letterSet(0), _demand(0), _sum(sum) { //ASSERT(letters.size() == count.size()); for (int i = 0; i < letters.size(); ++ i) _letterSet |= (1 << letters[i]); _demand = _letterSet; // all letters must be distinct: //ASSERT(letters.size() == bitcount(_letterSet)); } CharLinCtr(IFILE in) { _letterSet = RDINT(in); _demand = RDINT(in); _sum = RDINT(in); RDVECT(in, _letters); RDVECT(in, _coeffs); } void dump(OFILE out) { WRINT(out, _letterSet); WRINT(out, _demand); WRINT(out, _sum); WRVECT(out, _letters); WRVECT(out, _coeffs); } void propagate(); // propagate() is not virtual, as this class is not a PpgNode SET getDemand() { return _demand; } // this method used to have a more complicate implementation... public: vector _letters; vector _coeffs; SET _letterSet; SET _demand; int _sum; }; //---------------------------------------------------------------------- // INFORMATION PER GUESS //---------------------------------------------------------------------- class Solution { public: Solution(const char* word) : _letterSet(0) { memcpy(_word, word, WordLength); for (int i = 0; i < WordLength; ++ i) _letterSet |= (1 << word[i]); } void eval(const char* guess, int & hit, int & miss) const { hit = miss = 0; for (int i = 0; i < WordLength; ++ i) if (guess[i] == _word[i]) ++ hit; else if (! BIT(guess[i], _letterSet)) ++ miss; } char _word[MAX_LENGTH]; int _letterSet; }; //---------------------------------------------------------------------- class GuessHitSet : public SetVar { public: GuessHitSet(Guess* g) : _g(g), SetVar(WordLength) {} GuessHitSet(Guess* g, IFILE in) : _g(g), SetVar(in) {} virtual void propagate(int); private: Guess* _g; }; class Guess { public: Guess(const char* guess, int hit, int miss) : _numHits(hit), _numMisses(miss), _hitSet(new GuessHitSet(this)) { memcpy(_word, guess, WordLength); } Guess(IFILE in) { RDWORD(in, _word); _numHits = RDINT(in); _numMisses = RDINT(in); _hitSet = new GuessHitSet(this, in); } void initPpg() { S->freeze(); SET su, po; su = po = 0; for (int pos = 0; pos < WordLength; ++ pos) { SetVar* lp = theLetterAtPos[pos]; SET bit = (1 << _word[pos]); if (bit & lp->sure) _hitSet->include(1 << pos); else if (bit & ~ lp->possible) _hitSet->remove(1 << pos); } S->unfreeze(); _hitSet->initPpg(_numHits); } void dump(OFILE out) { WRWORD(out, _word); WRINT(out, _numHits); WRINT(out, _numMisses); _hitSet->dump(out); } bool accepts(Solution & sol) { int h,m; sol.eval(_word, h, m); return h == _numHits && m == _numMisses; } //-- the guess char _word[MAX_LENGTH]; //-- its score int _numHits; int _numMisses; //-- other stored information GuessHitSet* _hitSet; private: // forbidden operations Guess(const Guess &); void operator = (const Guess &); }; //---------------------------------------------------------------------- // INFORMATION BY LETTER INDEX (POSITION) //---------------------------------------------------------------------- class LetterAtPos : public SetVar { public: LetterAtPos(int i) : _position(i), SetVar(26) {} LetterAtPos(int i, IFILE in) : _position(i), SetVar(in) { } void dump(OFILE out) { SetVar::dump(out); } virtual void propagate(int); private: int _position; // position in the word }; //---------------------------------------------------------------------- // GLOBAL INFORMATION //---------------------------------------------------------------------- class PossibleLetters : public SetVar { public: PossibleLetters() : SetVar(26) {} PossibleLetters(IFILE in) : SetVar(in) {} virtual void propagate(int); //no specific data }; //---------------------------------------------------------------------- // GLOBAL INFORMATION CONSTRAINT //---------------------------------------------------------------------- // constraint: the "possible letters" set is the union of each // "letter at pos" set. No initial propagation as the constraint // is supposed to begin its life in a coherent state. class LetterUnionCtr : public PpgNode { public: void propagate(int) // override { // computing which letters... SET sure = 0; // ... are sure at (at least) one position SET poss_once = 0; // ... are possible at (at least) one position SET poss_more = 0; // ... are possible at more than one position for (int iPos = 0; iPos < WordLength; ++ iPos) { SetVar* lp = theLetterAtPos[iPos]; //--- propagation towards single letters, 1: possible lp->remove(~ theLetters->possible); if (! S->ok()) return; SET poss = lp->possible; poss_more |= (poss & poss_once); poss_once |= poss; sure |= lp->sure; } //--- propagation towards possibleLetters theLetters->include(sure); theLetters->remove(~ poss_once); if (! S->ok()) return; //--- propagation towards single letters, 2: sure // letters that are sure as PossibleLetter, // and that are possible at one position, // but no more than one position, are sure at this position; // but we don't need to propagate if this is already known. SET what = theLetters->sure & ~ poss_more & ~ sure; // no need to add "& poss_once": //ASSERT(what == (what & poss_once)); // otherwise, possibleLetters->remove(~ poss_once) would have failed. if (what) for (int iPos = 0; iPos < WordLength; ++ iPos) { SetVar* lt = theLetterAtPos[iPos]; //ASSERT(set_inclusion(lt->possible & what, ~ poss_more)); lt->include(lt->possible & what); if (! S->ok()) return; } } };//end class //---------------------------------------------------------------------- //---------------------------------------------------------------------- // T H E S A M P L E & E V A L U A T O R M O D U L E //---------------------------------------------------------------------- //---------------------------------------------------------------------- double GetBiProba(int i, int j); //@@TEMP@@ defined at end of file double Englishness(const char* word) { double bp = GetBiProba(26, word[0]) * GetBiProba(word[WordLength-1], 26); for (int i = 1; i < WordLength; ++ i) // i from ONE to WordLength - 1 bp *= GetBiProba(word[i-1], word[i]); return bp; } class SampleItem : public Solution { public: SampleItem(const char* word, double eng) : Solution(word), englishness(eng) {} double englishness; }; class NewGuess { public: NewGuess(const char* word) { memcpy(_word, word, WordLength); clearEval(); } void clearEval() { double z(0.); for (int h = 0; h <= WordLength; ++ h) for (int m = 0; m <= WordLength - h; ++ m) _count[h][m]=z; } void addEval(const SampleItem & sol) { int hit, miss; sol.eval(_word, hit, miss); _count[hit][miss] += sol.englishness; } double getBareVal() const { double sum = 0.0; for (int h = 0; h <= WordLength; ++ h) for (int m = 0; m <= WordLength - h; ++ m) { double c = _count[h][m]; if (c > 0.0) sum += c * log(c); // if c == 0, c*log(c) "morally" equals zero too. } return - sum; } //private: char _word[MAX_LENGTH]; double _count[MAX_LENGTH+1][MAX_LENGTH+1]; }; //------------------------------- // global vars for global algorithm vector Sample; double sampleTotal; vector Moves; NewGuess* Move; //====================================================================== //====================================================================== // CLASSES IMPLEMENTATION (... the sequel ...) //====================================================================== //====================================================================== //---------------------------------------------------------------------- //---------------------------------------------------------------------- // THE PROPAGATION METHODS //---------------------------------------------------------------------- //---------------------------------------------------------------------- void CharLinCtr::propagate() // non-virtual: this is not a PpgNode ! { int i; // sum of maxes, resp. mins, of the terms // (term == coefficient * (0 or 1)): int maxx = 0; int minn = 0; // 'max' and 'min' are reserved by the STL // so I don't use these names. for (i = 0; i < _coeffs.size(); ++ i) { int c = _coeffs[i]; int mask = (1 << _letters[i]); if (theLetters->sure & mask) minn += c; if (theLetters->possible & mask) maxx += c; } // "freedom": // by how much more each min or max of a term can be strengthened: int minF = _sum - minn; int maxF = maxx - _sum; if (minF < 0 || maxF < 0) { S->fail(); return; } // compute which letters to declare impossible (minus) or // sure (plus). int minus = 0, plus = 0; S->save(_demand); _demand &= (theLetters->possible & ~ theLetters->sure); // ctrMask: mask for summands that are not yet bound for (i = 0; i < _coeffs.size(); ++ i) { int letterBit = (1 << _letters[i]); if (letterBit & _demand) { if (minF < _coeffs[i]) minus |= letterBit; if (maxF < _coeffs[i]) plus |= letterBit; // both conditions can be true at the same time ! // in which case we've hit a contradiction. } } theLetters->remove(minus); if (S->ok()) theLetters->include(plus); } //---------------------------------------------------------------------- // guess propagation //---------------------------------------------------------------------- void GuessHitSet::propagate(int change) { int added = change & sure; int removed = change & ~ possible; //--- G-LP ( hit[i] == (theLetters[i] == _word[i]) ): for (int i = 0; i < WordLength; ++ i) if (added & (1 << i)) theLetterAtPos[i]->setValue(_g->_word[i]); else if (removed & (1 << i)) theLetterAtPos[i]->removeValue(_g->_word[i]); //--- G:#H (number of hits) ppgCardinal(_g->_numHits); } //---------------------------------------------------------------------- void LetterAtPos::propagate(int changed) { if (ppgSingleton()) { // variable has become bound ! //ASSERT(is_singleton(sure) && (sure==possible)); // nothing to do here [old code removed due to lack of space] } //--- G-LP /for each guess, hit[i] == (theLetters[i] == _word[i]): for (int iGuess = 0; iGuess < theGuesses.size(); ++ iGuess) { Guess & g = * theGuesses[iGuess]; int letter = g._word[_position]; int letterMask = 1 << letter; if (letterMask & changed) { if (letterMask & sure) g._hitSet->include(1 << _position); else if (letterMask & ~ possible) g._hitSet->remove(1 << _position); } } //--- LP-L:U // (thePossibleLetters is union over i of letters at pos i) if (! S->ok()) return; S->trigger(unionCtr,1); // if (changed & sure) theLetters->include(sure); } //---------------------------------------------------------------------- void PossibleLetters::propagate(int changed) { //--- internal constraint: no more than WordLength present letters! if (sureCount > WordLength) { S->fail(); return; } else if (sureCount == WordLength) remove(~ sure); // linear constraints on the possible variables: for (int i = 0; i < theCharLinCtrs.size() && S->ok(); ++ i) if (changed & theCharLinCtrs[i]->_demand) theCharLinCtrs[i]->propagate(); // LP-L:U if (S->ok()) S->trigger(unionCtr,1); } //---------------------------------------------------------------------- //---------------------------------------------------------------------- // FORWARD CHECKING PROCEDURES //---------------------------------------------------------------------- //---------------------------------------------------------------------- bool SetVar::fwdCheck(SET dom) { dom &= (possible & ~ sure); if (dom == 0) return true; int i, imask; for (i = 0, imask = 1; imask <= dom; ++ i, imask <<= 1) if (imask & dom & possible & ~ sure) { S->undoPoint(); include(imask); bool ok_in = S->ok(); S->undo(); // remember S->undo clears the failure flag if (ok_in) S->undoPoint(); remove(imask); bool ok_out = S->ok(); if (ok_in) S->undo(); if (! ok_out) if (ok_in) include(imask); // just proved true by contradiction ! else return false; // with failure since ok_out == S->ok() } return true; } bool FwdChk_LetterAtPos(SET dom) { for (int i = 0; i < WordLength; ++ i) if (! theLetterAtPos[i]->fwdCheck(dom)) return false; return true; } //---------------------------------------------------------------------- //---------------------------------------------------------------------- // I N I T / C L O S E S U B P R O G R A M S //---------------------------------------------------------------------- //---------------------------------------------------------------------- #define MODE_W "wb" #define MODE_R "rb" // common init (beginning) void PreInit() { S = new Solver; _theLAPs_ = new SetVar* [WordLength + 4]; theLetterAtPos = _theLAPs_ + 2; // "dummy" letters instanciated to "space" (code 26) int sp = (1 << 26); theLetterAtPos[-2] = new SetVar(27, sp,sp); theLetterAtPos[-1] = new SetVar(27, sp,sp); theLetterAtPos[WordLength] = new SetVar(27, sp,sp); theLetterAtPos[WordLength + 1] = new SetVar(27, sp,sp); unionCtr = new LetterUnionCtr; } void Init(int length) { int i; WordLength = length; // must be done first ! GuessNo = 1; PreInit(); for (i = 0; i < WordLength; ++ i) theLetterAtPos[i] = new LetterAtPos(i); theLetters = new PossibleLetters; // constraints are supposed to be created in a coherent state // so there is no need for "initial propagation" here. #ifdef __BORLANDC__ // TEST // if this program is to be executed continuously without leaving: theGuesses.resize(0); theCharLinCtrs.resize(0); #endif } void Reinit() { int i; FILE* in = fopen("TEMP.SplitMoo.st", MODE_R); //-- general constants (must be done first !) WordLength = RDINT(in); PreInit(); GuessNo = RDINT(in); for (i = 0; i < WordLength; ++ i) theLetterAtPos[i] = new LetterAtPos(i, in); //-- theLetters = new PossibleLetters(in); //-- theGuesses.resize(RDINT(in)); for (i = 0; i < theGuesses.size(); ++ i) theGuesses[i] = new Guess(in); //-- theCharLinCtrs.resize(RDINT(in)); for (i = 0; i < theCharLinCtrs.size(); ++ i) theCharLinCtrs[i] = new CharLinCtr(in); fclose(in); } void Close() { int i; // if I'm out of allowed source space, // I might "forget" to delete some items... FILE* out = fopen("TEMP.SplitMoo.st", MODE_W); //-- general constants (must be done first !) WRINT(out, WordLength); WRINT(out, GuessNo); for (i = 0; i < WordLength; ++ i) { // thePositions[i] = new Position(in); theLetterAtPos[i]->dump(out); delete theLetterAtPos[i]; } //-- theLetters->dump(out); delete theLetters; //-- WRINT(out, theGuesses.size()); for (i = 0; i < theGuesses.size(); ++ i) { theGuesses[i]->dump(out); delete theGuesses[i]; } //-- WRINT(out, theCharLinCtrs.size()); for (i = 0; i < theCharLinCtrs.size(); ++ i) { theCharLinCtrs[i]->dump(out); delete theCharLinCtrs[i]; } fclose(out); delete unionCtr; delete S; } //---------------------------------------------------------------------- // "Populate": the solution search algorithm //---------------------------------------------------------------------- // extract a random bit from the "max" lowest bits of "bits": // pre-condition: 1 <= max <= bitcount(bits) // (this implies that 'bits' is not empty) SET rndbit(SET bits, int max) { //ASSERT(1 <= max && max <= bitcount(bits)); int r = rand() % max; for (int i = 0; i < r; ++ i) bits &= (bits-1); //ASSERT(bits); return bits & ~ (bits-1); } SET rndbit(SET bits) { return rndbit(bits, bitcount(bits)); } //--- implicit parameters for Populate --- int Popul_timeout; // mytime100() after which Populate must stop bool Popul_random; // must Populate randomise its search ? bool Popul_stop; // set to 1 (on timeout or else) to stop the search //--- end // Populate(SET): main recursive search function: // the almost-main function for searching solutions void Populate(SET vars); //--- SOLUTION SEARCH HEURISTIC --- // get the 'best' values to instanciate from dom SET GetFirstDemand(SET dom) { // (1) try letter that is surely present, but not yet placed: SET best = dom & theLetters->sure; for (int i = 0; best && i < WordLength; ++ i) best &= ~ theLetterAtPos[i]->sure; //ASSERT(set_inclusion(best,dom)); if (best == 0) { SET d1, d2; // demanded (at least) once, twice. d1 = d2 = 0; // (2) try letter that may help satisfy unsatisfied CharLinCtrs for (int i = 0; i < theCharLinCtrs.size(); ++ i) { SET d = theCharLinCtrs[i]->getDemand(); d2 |= (d & d1); d1 |= d; } d2 &= dom; d1 &= dom; best = d2 ? d2 : d1; // I don't think this complication about favorizing letters // demanded at least twice is useful ? // (Second thought: yes, it may be useful, actually) // Anyway it does not do much harm ! } if (best == 0) best = dom; // we could (should?) return zero actually... return best; } // "Bottom" level of our algorithm: if we get here, we have found // a solution, we just have to handle it. void PopulateBottom() { // found a word satisfying the propagating constraints ! char word[MAX_LENGTH+1]; //@ '+1' for debug only for (int i = 0; i < WordLength; ++ i) word[i] = theLetterAtPos[i]->getValue(); double e = Englishness(word); // tell to stop the search... if (Popul_random) Popul_stop = true; // NB: we stop the search even if the word is not english. // Because englishness is not taken into account by the // propagation, it can often be the case that the englishness failure // occurred soon in the search, so that no "english" solution is // too be found near this one. We don't have time to waste insisting. if (e > 0.0) { SampleItem* s = new SampleItem(word, e); //@@@ temp ! (was) Sample.push_back(s); sampleTotal += s->englishness; // reevaluate previous "new guesses" for (int iM = 0; iM < Moves.size(); ++ iM) Moves[iM]->addEval(*s); } // evaluate new guess NewGuess* ng = new NewGuess(word); for (int iS = 0; iS < Sample.size(); ++ iS) ng->addEval(*Sample[iS]); // add new guess Moves.push_back(ng); } // try instantiating var and 'other_vars', // by trying each 'value' for 'var' // then proceeding instantiating 'other_vars' // on return 'value' is removed from the domain of 'var', this may // leave S in a failed state. void TryValue(SET other_vars, SetVar* var, SET value) { S->undoPoint(); var->include(value); // beware of possible failure... if (S->ok()) Populate(other_vars); S->undo(); if (! Popul_stop) var->remove(value); } // try instantiating 'vars', // by trying each value in "dom" for var #iVar // then proceeding to instantiate other 'vars' // once a value of "dom" has been tried it is removed from the domain // (hence) on return "dom" is removed from the domain, this may // leave S in a failed state. // "dom" may contain illegal values, and is trimmed. void TryVariable(SET vars, int iVar, SET dom) { SetVar* var = theLetterAtPos[iVar]; // 'dom' intersects 'var's domain: dom &= var->possible; if (! dom) return; //ASSERT(vars & (1 << iVar)); // iVar must belong to vars SET other_vars = vars ^ (1 << iVar); if (Popul_random) { // try all values in a random order SET remain(dom); int count = bitcount(remain); while (remain && ! Popul_stop && S->ok()) { SET val = rndbit(remain, count); TryValue(other_vars, var, val); remain ^= val; -- count; } } else // try each value in turn { while (dom && ! Popul_stop && S->ok()) { TryValue(other_vars, var, dom & ~ (dom - 1)); dom &= (dom - 1); // trick: look at bitcount()'s implementation for // insight on the meaning of this !!! } } // if place is tight, the two above cases can be merged ! // but there's only a few bytes to gain... } // try instantiating 'vars', // by trying each value in "dom" for each var in 'best_vars' // then proceeding instantiating other 'vars' // once a value has been tried for a variable it is removed from its domain // (hence) on return "dom" is removed from the domain of each 'best_var', // this may leave S in a failed state. // "dom" may contain illegal values, and is trimmed. void TryVariables(SET vars, SET best_vars, SET dom) { //ASSERT(vars); //ASSERT(dom); if (Popul_random) { SET choice(best_vars); int count = bitcount(choice); while (choice && ! Popul_stop && S->ok()) { //ASSERT(bitcount(choice) == count); // loop invariant int bit = rndbit(choice, count); TryVariable(vars, singleton_value(bit), dom); choice &= ~ bit; -- count; } } else { for (int i = 0; i < WordLength && ! Popul_stop && S->ok(); ++ i) { if (best_vars & (1 << i)) TryVariable(vars, i, dom); } } } void Populate(SET vars) //IN: vars: variables to instanciate { if (vars == 0) { PopulateBottom(); return; } time_check(Popul_timeout, 10, Popul_stop); if (Popul_stop) return; //-- heuristic: SET values = GetFirstDemand((1 << 26)-1); //SET other_vars = vars; if (values) { SET best_vars = 0; for (int i = 0; i < WordLength; ++ i) { SET var = (vars & (1 << i)); // var: mask for the i-th var if it is in vars if (var && (values & theLetterAtPos[i]->possible)) best_vars |= var; } TryVariables(vars, best_vars, values); if (Popul_stop) return; TryVariables(vars, best_vars, ~ values); if (Popul_stop) return; TryVariables(vars, vars & ~ best_vars, -1); } else TryVariables(vars, vars, -1); } // the main search function : void Populate() { S->undoPoint(); Populate((1 << WordLength) - 1); S->undo(); } //---------------------------------------------------------------------- //---------------------------------------------------------------------- // |\ /| / \ -+- |\ | // | \ / | / \ | | \ | (...sort of. Things are // | V | +---+ | | \ | not really well ordered // | | | | | | \| in this program...) // | | | | -+- | | //---------------------------------------------------------------------- //---------------------------------------------------------------------- const char* opening[6][10][10] = { //--- 4 --- "EASE"; "AEAS" is better {{"SENA","STET","DELL","SOOT","ROOT"}, {"CARS","LESS","ROLE","LIRA"}, {"FATS","CARL","TATH"}, {"BYRE","EALE"}, {"EASE"}}, //--- 5 --- {{"SPTPE","TRARE","ALATE","TRAIT","ALOAL","NIOOO"}, {"PETTR","TERRS","SLEEL","AOLAS","OLOTS"}, {"SABER","RATEA","SOSEL","LASII"}, {"SACZT","SATTL","TATDN"}, {"PNLEL","EASES"}, {"EARES"}}, //--- 6 --- {{"DDEATS","TRIERT","LOALAS","IOINTS","NERINE","NINNIE","OINING"}, {"CATTRS","TERTES","TETIES","STOOLS","LERLED","TINNED"}, {"RANCES","ALASED","IISTED","LARDDN","LIRTIR"}, {"STALLC","ROSTOR","DEUSUS","BLRTNL"}, {"RWSTLL","SOLTTR","SOSSES"}, {"GASSER","SASSED"}, {"SASSER"}}, //--- 7 --- "SERRIER" {{"TINTERL","LASTARS","TRELALE","NISTENS","LALLETS","LALLATE", "LALLOAS","LALLOON"}, {"TRAANES","SAATERS","TREATED","TINITES","TATTLES","TATTLED", "TANLTNG"}, {"TADITES","TAASSER","ALATTER","TITLIES","STELLED","LTALING"}, {"DNITTER","NATTITR","REARTET","TEELNNS","DSTTLAS"}, {"STAAAOR","TARLIAD","PEANJLR","SEITIES"}, {"PECRUFS","HEEAABD",0 }, {"SERRIED","MERPIRR"}, {"SERRIER"}}, //--- 8 --- "TATTATES": NB "RARRARES" is better w.r.t. my heuristic {{"RLARIIIT","RENERATE","SORRIEST","ERERTINE","SNEALINE","REARRINE", "SREERINE","RERELINE","CULLLING"}, {"RRANIERS","RNRERITE","ROESTERS","CERERINE","CEARLERS","EERALLED", "OOROLERS","RNNORRED"}, {"RRANIERS","RARERIRE","SEITIERS","RNOORTER","RARILERL","DARDENER", "IORRISII"}, {"TARINERS","CARTERED","RERTINII","UOTRERER","CARICIII","GGLSRRDS"}, {"IRILATRI","DNTTENER","OITNITES","TITTERER","CCRRCGCD"}, {"IANTNGCI","TARTDNED","TEKTITES", 0}, {"SINRCTIP",0,0}, {0,0}, {"TATTATES"}}, //--- 9 --- "TARTTATES" {{"SCANNIEST","INIRIRATE","LEANATINN","REDERTINE","CNNNOTITE", "REDRARINE","AELLALING","ROORERING","EEOELLING","LNLLODING"}, {"ONIIITORS","SEITININE","RORISTIRS","EENTERINE","SELALIERS", "LEALLIEES","DENEINERS","SNSSINSED","SULLLINGS"}, {"CININLENS","NANIONINS","SORRIIERS","SNSSINTED","ANNAAISED", "INNALINES","ROONERIER","NINILISIS"}, {"SNLINIIED","NENINATED","INNILITED","IINITINOS","CARAIIELC", "SIILLANEI","PERCIOICC"}, {"IUNIRATED","SLLICATEL","PILITATID","OLOTEIODS","CACAOILLC", "NLRDYDRDP"}, {"PIOORATOM","MELITIVIS","TORTOISES",0,"CELACNGAN"}, {"TRITIATES","SANITATES",0,0}, {0,0,0}, {0,"TARTRATES"}, {"TARTTATES"}} }; void GetOpening(char* sol, int hits, int misses) { strcpy(sol, opening[WordLength - 4][hits][misses]); sol[WordLength] = 0; } void GetOpening(char* sol) { GetOpening(sol, WordLength, 0); } void Solve(char sol[MAX_LENGTH+1], const char* given_guess, int hits, int misses) { int i; //------- incorporate guess information -------- char guess[MAX_LENGTH]; memcpy(guess, given_guess, WordLength); Translate(guess); // "Guess" constraint theGuesses.push_back(new Guess(guess, hits, misses)); theGuesses.back()->initPpg(); //ASSERT(S->ok()); // linear constraint vector count; count.resize(26,0); for (i = 0; i < WordLength; ++ i) ++ count[guess[i]]; vector letters; vector rept; for (i = 0; i < 26; ++ i) if (count[i]) { letters.push_back(i); rept.push_back(count[i]); } theCharLinCtrs.push_back( new CharLinCtr(letters, rept, WordLength - misses)); theCharLinCtrs.back()->propagate(); // initPpg // extensive forward checking ! // (could be reduced, but I have the time to do it) theLetters->fwdCheck(-1); SET lindem = 0; for (i = 0; i < theCharLinCtrs.size(); ++ i) lindem |= theCharLinCtrs[i]->getDemand(); FwdChk_LetterAtPos(lindem ? lindem : -1); // all constraints must be consistent : //ASSERT(S->ok()); //------- special case: opening -------- if (GuessNo == 2) { // we have almost 10 unused seconds here ! // further work could be done... GetOpening(sol, hits, misses); return; } //------- compute possible combinations ------- Sample.resize(0); sampleTotal = 0.0; Moves.resize(0); Popul_stop = 0; Popul_random = 0; Popul_timeout = 400; Populate(); // if Popul_stop != 0 then last search has been exhaustive // (since it has not been stopped by timeout) bool complete = ! Popul_stop; if (! complete && Sample.size() >= 10) { // reset sample because we want as unbiased a sample as // possible... // (but don't reset too small samples; they may indicate // it is difficult to find solutions) Sample.resize(0); sampleTotal = 0; for (int i = 0; i < Moves.size(); ++ i) Moves[i]->clearEval(); // reinitialises moves evaluation } while (Popul_stop && mytime100() < 800) { //srand(6+103*Sample.size()); //@@@ temp Popul_stop = 0; Popul_random = 1; Popul_timeout = mytime100() + 30;//20 Populate(); } //------- choose best possible combination // ASSERT(Moves.size() == Sample.size()); // no more true ! double bestAbsVal = -1e40; for (int iM = 0; iM < Moves.size(); ++ iM) { double val = Moves[iM]->getBareVal(); if (val > bestAbsVal) bestAbsVal = val; } // delta: (approx.) advantage of playing a possible word; // if the exhaustive search could not complete, // this advantage is considered as negligible. // (...because then we cannot calculate it anyway ! We need the total // number of solutions, and we do not know it.) double delta = 0.0; if (complete && sampleTotal > 0.0) { delta = log(sampleTotal) + bestAbsVal / sampleTotal; // _normally_ delta >= 0.0; but a rounding error can occur: if (delta < 0) delta = 0; } //ASSERT(delta >= 0.0); double bestVal = -1e40; int iBest = -1; for (int iM = 0; iM < Moves.size(); ++ iM) { double val = Moves[iM]->getBareVal() + delta * Moves[iM]->_count[WordLength][0]; if (val > bestVal) { iBest = iM; bestVal = val; } } //------- special cases: found nothing, or just one combination // (these cases render useless any sophisticated choice of guess) if (Sample.size() <= 1) { if (Sample.empty()) { for (int i = 0; i < WordLength; ++ i) sol[i] = singleton_value( rndbit(theLetterAtPos[i]->possible) ); } else memcpy(sol, Sample[rand() % Sample.size()]->_word, WordLength); // actually we could make more sophisticated here... Translate(sol); // delete moves return; } //------- generate and test other combinations // this is a poor man's genetic algorithm; I don't have the time // and the space to try to develop it. while (mytime100() < 980) { char word[MAX_LENGTH]; memcpy(word, Moves[rand() % Moves.size()]->_word, WordLength); for (int i = 0; i < WordLength; ++ i) if (rand() % 3) // 66% chances of mutations (yes, that's high !) word[i] = singleton_value(rndbit(theLetters->possible)); NewGuess* ng = new NewGuess(word); for (int iS = 0; iS < Sample.size(); ++ iS) ng->addEval(*Sample[iS]); double val = ng->getBareVal(); // was - delta; if (val > bestVal) { iBest = Moves.size(); Moves.push_back(ng); bestVal = val; } else delete ng; } //------- that's all if (iBest >= 0) memcpy(sol, Moves[iBest]->_word, WordLength); Translate(sol); // delete moves }; void main_first(::ostream & out, int length) { Init(length); char op[MAX_LENGTH + 1]; GetOpening(op); out << op << ::endl; Close(); } void main_next(::ostream & out, char* arg) { Reinit(); ++ GuessNo; char word[MAX_LENGTH+1]; Solve(word, arg, arg[WordLength+1] - '0', arg[WordLength+3] - '0'); word[WordLength] = 0; out << word << ::endl; Close(); } int main(int, char **) { char arg[100]; cin.getline(arg, 100); if (isdigit(arg[0])) main_first(cout, atoi(arg)); else main_next(cout, arg); return 0; } //=================== the English information ======================= #define BIPROBA_LEN 6 #define BIPROBA_COEFF 0.133651 #define BIPROBA_ZERO 72 const char uudata[] = { "EdecT#cW_U_ieiGbSidh_a_^_^#ebGNcHAHbTBfHEeB0aXPcMJ0X@M" "f8Y:b00k`0j`??h0RaT`b010]H#`NH]gNYOfT?#PKaI0]^B^PV0#Ii" "bZ`l]^[QWSTdagV_XmkcW_]eZUfaD;=`f:?d?8cDdI``a<;`RX`B0`aE_6O0]AgfI@AgN?=e=GVSKfE0YXZ_:R0a?^" "`#hcdaaG@KZdbo__X_geUcF[DeRa000#007ZK0004a00C20b0@0A0<" "]RDFfQ8UeDO#NWWI0P`GSFU0]0]gQT[jVPDi?ZfVGfUBD_ZbYL`DG0#H^bVbef#oUdXaTO[`STPcf[XWNZTa" "Z_a`U[`O]P^ediccShbbgcf^YZVeNIDeMFdeLEcHIeb0c][a0O0[4U" "B0000:00E00000:00030m000001h[[_iW[ThN^Y^[gXO#e_aZR0cLd" "^LaEdKFfaI]Y_U_a]B`i`>#0Z0nePYEjSIfhL>[PJeK5cc`aG[0bVc" "[a`^[[^@#MWefhQaEfedAT=YIXJb008i017e00>09_00I?0TQ00P=<" "eRDSaODac@VXL[aN0WZKI5E0RJTY?Y0^G@R_09KD3X#@=I[U:L0Z0X" "ZUVRZNSIZGN[#XX#9W]VMHSR6Si#>>3e0= 0 && temp < 64); if (temp > 0) _biProba[i][j] = exp(double(temp - BIPROBA_ZERO) * BIPROBA_COEFF); else _biProba[i][j] = 0; } } double GetBiProba(int i, int j) { static int inited = 0; if (! inited) { temp_init_biproba(); inited = 1; } return _biProba[i][j]; } // --- What's biproba ? // For each pair of letters x,y in A,B,C,...,Z,space // coded as follows: A=0,.. Z=25, space=26 // GetBiProba(i,j) == P(x.y) / sqrt(P(x) * P(y)) // where P(x) is the number of occurrences of x, // and P(x,y) is the number of occurrences of the sequence y // in my dictionary. // --- uudata encodes each "biproba" on 6 bits. #ifdef __BORLANDC__ } // end namespace SM #endif