/** Find palindromic pangrams w/conclusive info about where they aren't */
#include <iostream>
#include <vector>
#include <map>
#include <list>
#include <string>
#include <sstream>
#include <bitset>
#include <algorithm>
#include <fstream>
#include <ctime>
using namespace std;

typedef unsigned short int wid; // wid 0 is reserved
enum Direction { forw=0, back=1 };

const char* FORWARD_LIST = "WORD.LST";
const char* REVERSE_LIST = "REVW.LST";
const char* WOBBLES = "SELFSYM.LST";
const char* EMBEDS = "EMBEDS.LST";
const char* FIRST_LETTERS = "FST.LST";
const char* LAST_LETTERS = "LST.LST";
const char* PAL_MD_OUT = "PAL_MD.OUT";
const char* PAL_NMD_OUT = "PAL_NMD.OUT";
const char* FRAG_OUT = "FRAG.OUT";

const bool PRINT_PERIODICALLY = true;
const bool PRINT_EACH = false;
const bool CEASE_MSGS = false;
const int REPORT_PERIOD = 5; // seconds
const int INIT_CEILING = 20; // at 16, no middivs contain 'q'

typedef signed char schar;
typedef unsigned char uchar;
typedef unsigned long ulong;

/** A LexTree contains every word of the alphabet with a letter at each
node.  To determine whether a word is in the dictionary, you would traverse
the tree (starting at the root) to a depth equal to the length of the word, 
checking at each node that an appropriate IndexEntry exists. */

struct LexTree;
struct IndexEntry {
  char letter;
  wid id; // nonzero if the string spelled out so far is a word
  LexTree* continuation;
  IndexEntry(char c, wid word_id);
  template<class Itor>
  void fileWord(const wid word_id, const Itor& terminus, Itor current);
};

struct LexTree {
  vector<IndexEntry> entries;
  template<class Itor>
  void addWord(const wid id, const Itor& terminus, Itor current) {
    if(current != terminus) {
      if(entries.empty() || (entries.back().letter != *current)) {
        IndexEntry ie(*current, (current==terminus-1) ? id : 0);
        entries.push_back(ie);
      }
      entries.back().fileWord(id, terminus, current);
    }
  }
};

IndexEntry::IndexEntry(char c, wid word_id) {
  letter = c;
  id = word_id;
  continuation = new LexTree;
}

template<class Itor>
void IndexEntry::fileWord(const wid word_id, const Itor& terminus,
                          Itor current)
{
  continuation->addWord(word_id, terminus, ++current);
}

/** Build palindromes from the center to the edges, stopping when a
palindrome is first encountered.  The search is exhaustive.  In order to 
reduce branching, all possible successor words are precomputed, and they 
are either listed or referred to by a pointer to a LexTree.  For instance,
if the current working palindrome is "igloo" with the center of the 
prospective palindrome lying between the two o's, then the successor words 
would be "l", "lg" (if such words existed), or the pattern "lgi*", where "*"
stands for zero or more letters.  This pattern is represented by a pointer to 
a LexTree.  The pseudowords "l" and "lg" are said to be embedded.

We define the balance of a working palindrome to be the number of letters 
right of center minus the number of letters left of center.  In the igloo 
example, the balance is 1 - 4 = -3.  A Hotel is a repository for successor 
information.  It keeps a list of embedded words and a pointer to a LexTree 
for each word and possible balance.  It's just an array of arrays of
varying size.
*/

struct HotelRoom {
  vector<wid> embeds;
  LexTree* pattern;
};

struct HotelFloor {
  vector<HotelRoom> pos, neg;
  HotelRoom& operator[](int i) {
    return (i<0 ? neg[-i-1] : pos[i-1]);
  }
  void resize(int i) {
    pos.resize(i);
    neg.resize(i);
  }
};

struct Hotel {
  vector<HotelFloor> floors;  // indexed by wid; elt 0 is unused

  /** Given a pattern, find the corresponding subtree */
  LexTree* findTree(LexTree* lexTree, list<char> chars) {
    if(!lexTree || chars.empty())
      return lexTree;
    vector<IndexEntry>& entries = lexTree->entries;
    for(int i=0; i<entries.size(); ++i) {
      if(entries[i].letter == chars.front()) {
        chars.pop_front();
        return findTree(entries[i].continuation, chars);
      }
    }
    return 0;
  }

  Hotel(const vector<int>& wordlength, LexTree lexTree[2],
        map<string, wid>& widOf, vector<string>& wordlist)
    : floors(wordlength.size())
  {
    cout << "Initializing hotel; floors has size " << floors.size() << endl;
    for(int i=1; i<floors.size(); i++) {
      int wl = wordlength[i];
      //if(i%1000 == 0) cout << ".";
      floors[i].resize(wl);
      string s = wordlist[i];

      // example: word "old"; bal -1: o*; -2: lo*; -3: dlo*
      list<char> chars;
      for(string::iterator j=s.begin(); j!=s.end(); ++j) // TODO: See 18.6.1
        chars.push_front(*j);
      for(int j=0; j<wl; ++j) {
        floors[i][j-wl].pattern = findTree(&lexTree[forw], chars);
        chars.pop_front();
      }

      // bal +1: *d; +2: *dl; +3: *dlo => (REV) d*, ld*, old*
      if(!chars.empty()) cout << "UNEXPECTED CHARS" << endl;
      for(string::iterator j=s.begin(); j!=s.end(); ++j)
        chars.push_back(*j);
      for(int j=0; j<wl; ++j) {
        floors[i][wl-j].pattern = findTree(&lexTree[back], chars);
        chars.pop_front();
      }
    }
    cout << "Resized floors and harvested lexical tree links" << endl;

    // format: wid pos neg word
    cout << "Reading embed file" << endl;
    ifstream embeds(EMBEDS);
    wid id;
    int pos, neg;
    string word;
    while(embeds) {
      embeds >> id >> pos >> neg >> word;
      HotelFloor& floor = floors[id];
      wid embid = widOf[word];
      //cout<<id<<" "<<pos<<" "<<neg<<" "<<word<<"("<<embid<<")"<<endl;
      floor[pos].embeds.push_back(embid);
      floor[neg].embeds.push_back(embid);
    }
    cout << "Finished initializing hotel" << endl;
  }
};

inline string to_str(bitset<26> b) {
  return b.template to_string<char, char_traits<char>, allocator<char> >();
}

/** Where the most central word boundary in the prospective palindrome lies
    with respect to the axis of symmetry */
enum CentralDivider { MORE_LEFT, JUST_LEFT, MIDDLE, 
		      JUST_RIGHT, MORE_RIGHT, NA }; 

/** In order to reduce branching, we stop when a prospective palindrome has 
balance 1 or -1.  Palindrome fragments can be assembled into palindromes.
Fragments are classified into the categories below.

Put another way, a palindrome can be decomposed into smaller palindromes, 
and further, into fragments.  A palindrome is called atomic if it is not made 
of smaller palindromes (that is, if no word boundary is mapped to another upon 
reflection about the palindrome's axis of symmetry).  Each atomic component of 
a palindrome except the center-most one must have a word boundary at the axis 
of symmetry (e.g., "am ma") and therefore be of even length.  The center-most 
atomic component may be of even or odd length.

Each atomic component may be further decomposed not where reflected word 
boundaries line up exactly, but where they are off by one letter.  For
example, "ma ha ham" may be decomposed into fragments "ma ham" (which happens
to be a palindrome on its own) and "ha".  The axis of symmetry is located at 
the central "a", and the image of the word boundary between "ma" and "ha" is 
just one letter from the word boundary between "ha" and "ham" upon reflection.
The fragments are joined at binding letter "h".  In order for two fragments to 
fit together, they must have a binding letter in common, and they must be of 
compatible "polarity."

The outer fragment under this decomposition is called a Lell.  There may be
any number of "bridge types," specifically "Tees" or "Esses," depending on 
their shapes, and optionally a central Ell (either another Lell or a Rell).
The palindrome has a word boundary in the center if and only if a central
Ell is present and is a Lell. 
*/
enum FragmentType { LellPos, LellNeg, // starter types
		    Tee, Less, Ress,  // bridge types
		    RellPos, RellNeg, // closer only 
                    N };

struct FragmentPK {
  bitset<26> letterSet;
  FragmentType type;  
  char b1, b2;

  FragmentPK(bitset<26> lSet, FragmentType ft, char bc1, char bc2) {
    letterSet = lSet;
    type = ft;
    b1 = bc1;
    b2 = bc2;
  }
};

bool operator<(const FragmentPK& f1, const FragmentPK& f2) {
  const ulong& set1 = f1.letterSet.to_ulong();
  const ulong& set2 = f2.letterSet.to_ulong();

  if(set1<set2)
    return true;
  else if(set1>set2)
    return false;
  if(f1.type<f2.type)
    return true;
  else if(f1.type>f2.type)
    return false;
  if(f1.b1<f2.b1)
    return true;
  else if(f1.b1>f2.b1)
    return false;
  return f1.b2<f2.b2;
}
bool operator>(const FragmentPK& f1, const FragmentPK& f2) { 
  return f2<f1; 
}
bool operator!=(const FragmentPK& f1, const FragmentPK& f2) {
  return f1<f2 || f1>f2;
}
bool operator==(const FragmentPK& f1, const FragmentPK& f2) {
  return !(f1!=f2);
}
bool operator<=(const FragmentPK& f1, const FragmentPK& f2) {
  return !(f1>f2);
}
bool operator>=(const FragmentPK& f1, const FragmentPK& f2) {
  return !(f1<f2);
}

/** Maintain the state of our exhaustive traversal of the tree of successor
words */
class ExploreTree {
  int ceiling; // max palindrome length
  vector<wid> wordStack; // the stack of successor words
  vector<string>* wordlist;
  vector<int>* wordlength;
  Hotel* hotel;
  vector< pair<wid, int> >* wobbleStarts;
  vector< bitset<26> >* letterSets;
  bitset<26> completeLetterSet;
  time_t lastReport;
  int nodesSinceLastReport;
  schar* mpom;
  schar* mpon;
  string* mpoms;
  string* mpons;
  ulong balPosOneCount, balNegOneCount;
  vector<char>* firstLetter;
  vector<char>* lastLetter;
  map<FragmentPK, pair<schar, string> >* frags; 
  typedef map<FragmentPK, pair<schar, string> >::iterator MFI;

public:
  CentralDivider centralDivider;

  ExploreTree(vector<string>* wlist, vector<int>* wlen, Hotel* hot,
              vector< pair<wid, int> >* wobSt, vector< bitset<26> >* lSets,
	      vector<char>* fLetter, vector<char>* lLetter)
  {
    ceiling = INIT_CEILING;
    wordlist = wlist;
    wordlength = wlen;
    hotel = hot;
    wobbleStarts = wobSt;
    letterSets = lSets;
    completeLetterSet.flip(); // 0 when constructed, now set all to 1
    time(&lastReport);
    nodesSinceLastReport = 0;
    mpom = new schar[1<<26];
    mpon = new schar[1<<26];
    mpoms = new string[1<<26];
    mpons = new string[1<<26];
    for(int i=0; i<(1<<26); ++i)
      mpom[i] = mpon[i] = -1;
    balPosOneCount = balNegOneCount = 0;

    firstLetter = fLetter;
    lastLetter = lLetter;
    frags = new map<FragmentPK, pair<schar, string> >;
    cout << "Finished initializing ExploreTree" << endl;
  }

  inline void dumpWordStack(ostream& os) {
    for(vector<wid>::iterator i=wordStack.begin(); i!=wordStack.end(); ++i)
      os<<" "<<(*wordlist)[*i];
  }

  inline void dumpWordStack() {
    dumpWordStack(cout);
    cout<<endl;
  }

  inline void outLine(wid id, string& s, int length, int balance,
                      bitset<26> letterSet, int tabs = 0)
  {
    for(int i=0; i<tabs%40; ++i) cout << " ";
    cout << id << "[" << s << "] " << length << (balance>0 ? "+" : "")
         << (balance==0 ? "=" : "") << balance << " " << letterSet << endl;
  }

  inline void tryWid(wid id, int balance, int length, bitset<26> letterSet,
                     int tabs)
  {
    wordStack.push_back(id);
    if(balance>=0)
      go(balance - (*wordlength)[id], length + (*wordlength)[id],
         letterSet | (*letterSets)[id], tabs+1);
    if(balance<=0)
      go(balance + (*wordlength)[id], length + (*wordlength)[id],
         letterSet | (*letterSets)[id], tabs+1);
    wordStack.pop_back();
  }

  void goEvery(LexTree* lexTree, int balance, int length,
               bitset<26> letterSet, int tabs)
  {
    if(lexTree) {
      for(vector<IndexEntry>::iterator i=lexTree->entries.begin();
          i!=lexTree->entries.end(); ++i)
      {
        if(i->id)
          tryWid(i->id, balance, length, letterSet, tabs);
        goEvery(i->continuation, balance, length, letterSet, tabs);
      }
    }
  }

  inline int abs(int i) { return (i<0 ? -i : i); }

  void insertPal(bitset<26> letterSet, int length) {
    schar* mpo = (centralDivider==MIDDLE ? mpom : mpon);
    string* mpos = (centralDivider==MIDDLE ? mpoms : mpons);
    ulong uls = letterSet.to_ulong();
    //cout<<"DBG: Encountered letterSet "<<uls<<endl;
    if(mpo[uls]==-1 || mpo[uls]>length) {
      mpo[uls] = length;
      ostringstream ss;
      dumpWordStack(ss);
      mpos[uls] = ss.str();
    }
  }

  void catalogFragment(int balance, int length, bitset<26> letterSet) {
    FragmentType type;
    char b1, b2;
    if(length%2==1 && centralDivider==MIDDLE && balance>0) {
      type = LellPos;
      b1 = (*lastLetter)[wordStack.back()]; // last char of top word
      b2 = 0;
    } else if(length%2==1 && centralDivider==MIDDLE && balance<0) {
      type = LellNeg;
      b1 = (*firstLetter)[wordStack.back()]; // first char of top word
      b2 = 0;
    } else if(length%2==0 && centralDivider==JUST_LEFT && balance>0) {
      type = Tee;
      b1 = (*firstLetter)[wordStack.front()]; // first char of bottom word
      b2 = (*lastLetter)[wordStack.back()]; // last char of top word 
    } else if(length%2==0 && centralDivider==JUST_LEFT && balance<0) {
      type = Ress;
      b1 = (*firstLetter)[wordStack.back()]; // first char of top word
      b2 = (*firstLetter)[wordStack.front()]; // first char of bottom word
      if(b1>b2) swap(b1, b2);
    } else if(length%2==0 && centralDivider==JUST_RIGHT && balance>0) {
      type = Less;
      b1 = (*lastLetter)[wordStack.front()]; // last char of bottom word
      b2 = (*lastLetter)[wordStack.back()]; // last char of top word
      if(b1>b2) swap(b1, b2);
    } else if(length%2==0 && centralDivider==JUST_RIGHT && balance<0) {
      type = Tee;
      b1 = (*firstLetter)[wordStack.back()]; // first char of top word
      b2 = (*lastLetter)[wordStack.front()]; // last char of bottom word
    } else {
      if(balance>0) {
	type = RellPos;
	b1 = (*lastLetter)[wordStack.back()]; // last char of top word
      } else {
	type = RellNeg;
	b1 = (*firstLetter)[wordStack.back()]; // first char of top word
      }
      b2 = 0;
    }
    ostringstream words;
    dumpWordStack(words);

    FragmentPK pk(letterSet, type, b1, b2);
    MFI f = frags->find(pk);
    if(f==frags->end() || length<f->second.first) {
      //cout<<"Found superior fragment -- inserting."<<endl;
      frags->insert(pair<FragmentPK, pair<schar, string> >(pk, pair<schar, string>(length, words.str())));
    } 
  }

  /** Construct every possible palindrome up to a fixed length (ceiling).
   * When a palindrome is found (that is, when balance==0), we store it.
   * Similarly, when balance==1 or balance==-1, we store it as a fragment. */
  void go(int balance, int length, bitset<26> letterSet, int tabs) {
    // Print a report occasionally
    if(PRINT_PERIODICALLY) {
      nodesSinceLastReport++;
      time_t now;
      time(&now);
      if(PRINT_EACH || (difftime(now, lastReport) >= REPORT_PERIOD)) {
        cout<<"REPORT ("<<nodesSinceLastReport<<" new nodes) MD="<<
	  (centralDivider==MIDDLE?"Y":"N")<<"\n";
        dumpWordStack();
        lastReport = now;
        nodesSinceLastReport = 0;
      }
    }

    if(length>=ceiling) {
      if(CEASE_MSGS) cout << "cease (length>=ceiling)" << endl;
    } else {
      if(balance==0) {
	if(INIT_CEILING>=51 && letterSet==completeLetterSet) {
	  ceiling = length;
	  cout << "ATTN: New shortest palindrome len=" << length << ":";
	  dumpWordStack();
	} else {
	  insertPal(letterSet, length);
	}
      } else if(balance==1 || balance==-1) {
	(balance>0 ? balPosOneCount : balNegOneCount)++;
	catalogFragment(balance, length, letterSet);
      } else { // try all possible successors
        //int newCharRoom = (ceiling - length - abs(balance))/2;
        //int lettersNeeded = 26 - letterSet.count();
        //if(lettersNeeded>newCharRoom) {
	if(false) {
          //if(CEASE_MSGS) cout << "cease (lettersNeeded>newCharRoom)" << endl;
        } else { // hotel lookup
	  HotelRoom& room = hotel->floors[wordStack.back()][balance];
	  for(vector<wid>::iterator i=room.embeds.begin(); 
	      i!=room.embeds.end(); ++i)
          { // don't put embedded entry on stack
	    go(balance + (balance>0 ? -1 : 1) * (*wordlength)[*i],
	       length + (*wordlength)[*i], letterSet, tabs+1);
	  }
	  goEvery(room.pattern, balance, length, letterSet, tabs);
        }
      }
    }
  }

  void prepareFragments() {
    // These tables have as a parameter the binding letter 
    cout<<"\nBuilding fragment info"<<endl;
    ulong numType[FragmentType(N)];    
    for(int i=0; i<FragmentType(N); ++i)
      numType[i] = 0;
    for(MFI i=frags->begin(); i!=frags->end(); ++i)
      ++numType[i->first.type];

    cout<<"Fragment stats: LellPos="<<numType[LellPos]<<" LellNeg="<<
      numType[LellNeg]<<" RellPos="<<numType[RellPos]<<" RellNeg="<<
      numType[RellNeg]<<"\nTee="<<numType[Tee]<<" Less="<<numType[Less]<<
      " Ress="<<numType[Ress]<<" TOTAL="<<frags->size()<<endl;

    cout<<"Writing fragments to disk..."<<flush;
    ofstream fragfile(FRAG_OUT);
    for(MFI i=frags->begin(); i!=frags->end(); i++) {
      fragfile<<"B"<<to_str(i->first.letterSet)<<"\t"<<i->first.type<<"\t"<<
	i->first.b1<<"\t";
      if(i->first.b2)
	fragfile<<i->first.b2;
      else
	fragfile<<"NULL";
      fragfile<<"\t"<<int(i->second.first)<<"\t"<<i->second.second<<"\n";
    }
    cout<<" done."<<endl;
  }

  void explore() {
    /* There are two ways to start: with a self-symmetric word (a "wobble")
     * or with a plain old balance-0 empty stack. */

    cout << "Trying wobbleStarts" << endl;
    for(vector< pair<wid, int> >::iterator i=wobbleStarts->begin();
        i!=wobbleStarts->end(); ++i)
    {
      wordStack.push_back(i->first);
      if(i->second==0)
	centralDivider = NA;
      else switch((*wordlength)[i->first] - i->second) {
      case 0: centralDivider = MIDDLE; break;
      case 1: centralDivider = (i->second > 0 ? JUST_LEFT : JUST_RIGHT); break;
      default: centralDivider= (i->second > 0 ? MORE_LEFT : MORE_RIGHT); break;
      }
      go(i->second, (*wordlength)[i->first], (*letterSets)[i->first], 1);
      wordStack.pop_back();
    }

    // try the rest
    /* For each word, say its length is L, can start with balance
     * +/-L, +/-(L-1). */
    cout << "\nTrying otherStarts" << endl;
    for(int i=1; i<wordlist->size(); ++i) {
      wordStack.push_back(i);

      /* TODO: To prevent redundant computation, only allow a word to be
       * the first on the stack if it has no strict embeds. */
      centralDivider = MIDDLE;      
      HotelRoom& roomPos = hotel->floors[i][(*wordlength)[i]];
      go((*wordlength)[i], (*wordlength)[i], (*letterSets)[i], 1);
      HotelRoom& roomNeg = hotel->floors[i][-(*wordlength)[i]];
      go(-(*wordlength)[i], (*wordlength)[i], (*letterSets)[i], 1);

      centralDivider = JUST_LEFT;
      go((*wordlength)[i]-1, (*wordlength)[i], (*letterSets)[i], 1);

      centralDivider = JUST_RIGHT;
      go(-((*wordlength)[i]-1), (*wordlength)[i], (*letterSets)[i], 1);

      wordStack.pop_back();
    }

    cout<<"\nPreparing fragments"<<endl;
    prepareFragments();

    cout<<"Compiling stats:"<<endl;
    ulong nmpom = 0;
    ulong nmpon = 0;
    ofstream midDivs(PAL_MD_OUT);
    ofstream nonMidDivs(PAL_NMD_OUT);
    for(ulong i=0; i<(1<<26); ++i) {
      if(mpom[i] != -1) {
	nmpom++;
	midDivs<<"B"<<to_str(bitset<26>(i))<<"\t"<<int(mpom[i])<<"\t"<<
	  mpoms[i]<<"\n";
      } 
      if(mpon[i] != -1) {
	nmpon++;
	nonMidDivs<<"B"<<to_str(bitset<26>(i))<<"\t"<<int(mpon[i])<<"\t"<<
	  mpons[i]<<"\n";
      }
    }
    cout<<"Done exploring -- mpom has "<<nmpom<<" nonnulls, and mpon has "<<
      nmpon<<"; "<<balPosOneCount<<" +1 balances and "<<balNegOneCount<<
      " -1 balances"<<endl;
  }
};

bitset<26> detectLetters(string s) {
  bitset<26> letterSet;
  for(string::iterator i=s.begin(); i!=s.end(); ++i)
    letterSet.set(*i - 'a');
  return letterSet;
}

void initialize(vector<string>& wordlist, vector<int>& wordlength,
                vector< bitset<26> >& letterSets, map<string, wid>& widOf,
                LexTree lexTree[2])
{
  ifstream fdict(FORWARD_LIST);
  string gl;
  wid wordindex = 0;

  while(getline(fdict, gl)) {
    wordlist.push_back(gl);
    wordlength.push_back(gl.size());
    letterSets.push_back(detectLetters(gl));
    widOf[gl] = ++wordindex;
    lexTree[forw].addWord(wordindex, gl.end(), gl.begin());
  }
  cout << "Finished initializing forward lexical tree" << endl;

  ifstream bdict(REVERSE_LIST);
  while(bdict) {
    getline(bdict, gl);
    lexTree[back].addWord(widOf[gl], gl.rend(), gl.rbegin());
  }
  cout << "Finished initializing backward lexical tree" << endl;
}

int main() {
  // wordlist and length are indexed by wid
  vector<string> wordlist;
  vector<int> wordlength;
  vector< bitset<26> > letterSets;
  wordlist.push_back(""); // wid 0 is reserved
  wordlength.push_back(-1); // wid 0 is reserved
  letterSets.push_back(0); // wid 0 is reserved
  map<string, wid> widOf;
  widOf[""] = 0; // to match the initial element of wordlist
  LexTree lexTree[2];
  initialize(wordlist, wordlength, letterSets, widOf, lexTree);

  cout<<"sizeof(int)="<<sizeof(int)<<"; sizeof(ulong)="<<sizeof(ulong)<<
    "; sizeof(schar)="<<sizeof(schar)<<endl;
  cout<<"wordlist size: "<<wordlist.size()<<"; wordlength size: "<<
    wordlength.size()<<";\nwidOf size: "<<widOf.size()<<
    "; letterSets size: "<<letterSets.size()<<endl;

  Hotel hotel(wordlength, lexTree, widOf, wordlist);

  vector< pair<wid, int> > wobbleStarts;
  ifstream wobbles(WOBBLES);
  wid wob_id;
  int bal;
  while(wobbles) {
    wobbles >> wob_id >> bal;
    pair<wid, int> entry(wob_id, bal);
    wobbleStarts.push_back(entry);
  }
  cout<<"wobbleStarts size="<<wobbleStarts.size()<<endl;

  cout<<"Loading first/last letter vectors"<<endl;
  vector<char> firstLetter;
  vector<char> lastLetter;
  firstLetter.push_back(0);
  lastLetter.push_back(0);
  ifstream fltrs(FIRST_LETTERS);
  char c;
  while(fltrs) {
    fltrs>>c;
    firstLetter.push_back(c);
  }
  ifstream lltrs(LAST_LETTERS);
  while(lltrs) {
    lltrs>>c;
    lastLetter.push_back(c);
  }

  ExploreTree tree(&wordlist, &wordlength, &hotel, &wobbleStarts, &letterSets,
		   &firstLetter, &lastLetter);
  tree.explore();
}
