Advertisement
markgritter

Flood-it solver

Dec 8th, 2011
1,628
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 20.44 KB | None | 0 0
  1. #include <stdlib.h>
  2. #include <string.h>
  3. #include <stdint.h>
  4. #include <iostream>
  5. #include <algorithm>
  6. #include <vector>
  7. #include <fstream>
  8. #include <assert.h>
  9. #include <time.h>
  10. #include <map>
  11. #include <iomanip>
  12.  
  13. const int numColors = 6;
  14. const int problemSize = 14;
  15. const int boardSize = 25; // ceil( 14 * 14 / 8 )
  16.  
  17. int *problem[problemSize];
  18. uint8_t colorCount[numColors];
  19. char colorTranslation[numColors] = {
  20.     '0',
  21.     '1',
  22.     '2',
  23.     '3',
  24.     '4',
  25.     '5',
  26. };
  27.  
  28. void randomProblem( void ) {  
  29.     srandom( time( 0 ) );
  30.  
  31.     for ( int c = 0; c < numColors; ++c ) {
  32.         colorCount[c] = 0;
  33.     }
  34.     for ( int y = 0; y < problemSize; ++y ) {
  35.         problem[y] = new int[problemSize];
  36.         for ( int x = 0; x < problemSize; ++x ) {
  37.             problem[y][x] = random() % numColors;
  38.             colorCount[problem[y][x]] += 1;
  39.         }
  40.     }
  41. }
  42.  
  43. unsigned char bitToMask[] = {
  44.     0x1,
  45.     0x2,
  46.     0x4,
  47.     0x8,
  48.     0x10,
  49.     0x20,
  50.     0x40,
  51.     0x80
  52.  };
  53.  
  54. class BoardState {
  55. public:
  56.     unsigned char bits_[boardSize];
  57.     uint8_t colorsLeft_[numColors];
  58.  
  59.     static const BoardState emptyExample;
  60.     static const BoardState goalState;
  61.  
  62.     BoardState() {
  63.         memset( bits_, 0, boardSize );
  64.         for ( int c = 0; c < numColors; ++c ){
  65.             colorsLeft_[c] = colorCount[c];
  66.         }
  67.     }
  68.  
  69.     BoardState( bool ) {
  70.         int byte, mask;
  71.         coordinatesToBit( problemSize - 1, problemSize - 1, byte, mask );
  72.         memset( bits_, 0xff, byte );
  73.         bits_[byte] = mask | ( mask - 1 );
  74.  
  75.         for ( int c = 0; c < numColors; ++c ){
  76.             colorsLeft_[c] = 0;
  77.         }
  78.     }
  79.    
  80.     BoardState & operator=( const BoardState &other ) {
  81.         memcpy( bits_, other.bits_, boardSize );
  82.         for ( int c = 0; c < numColors; ++c ) {
  83.             colorsLeft_[c] = other.colorsLeft_[c];
  84.         }        
  85.         return *this;
  86.     }
  87.    
  88.     bool operator==( const BoardState &other ) const {
  89.         return memcmp( bits_, other.bits_, boardSize ) == 0;
  90.     }
  91.  
  92.     bool operator!=( const BoardState &other ) const {
  93.         return memcmp( bits_, other.bits_, boardSize ) != 0;
  94.     }
  95.  
  96.     bool empty(void) const {
  97.         return *this == emptyExample;
  98.     }
  99.    
  100.     void coordinatesToBit( int x, int y, int &byte, int &mask ) const {
  101.         int offset = y * problemSize + x;
  102.         byte = offset / 8;        
  103.         mask = bitToMask[ offset % 8 ];
  104.     }
  105.  
  106.     void set( int x, int y ) {
  107.         int byte, mask;
  108.         coordinatesToBit( x, y, byte, mask );
  109.         bits_[byte] |= mask;        
  110.         int c = problem[y][x];
  111.         colorsLeft_[c] -= 1;
  112.     }
  113.  
  114.     bool present( int x, int y ) const {
  115.         int byte, mask;
  116.         coordinatesToBit( x, y, byte, mask );
  117.         return bits_[byte] & mask;                
  118.     }
  119.  
  120.     int numColorsLeft( void ) const {
  121.         int total = 0;
  122.         for ( int c = 0; c < numColors; ++c ) {
  123.             if ( colorsLeft_[c] > 0 ) total += 1;
  124.         }        
  125.         return total;
  126.     }
  127.  
  128.     bool adjacentPresent( int x, int y ) const {
  129.         return ( x > 0 && present( x - 1, y ) ) ||
  130.             ( y > 0 && present( x, y - 1 ) ) ||
  131.             ( x < problemSize - 1 && present( x + 1, y ) ) ||
  132.             ( y < problemSize - 1 && present( x, y + 1 ) );
  133.     }
  134.  
  135.     void markDfs( int x, int y, int color ) {
  136.         if ( present( x, y ) ) return;
  137.  
  138.         set( x, y );
  139.         if ( x < problemSize - 1 && problem[y][x+1] == color )
  140.             markDfs( x+1, y, color );
  141.         if ( y < problemSize - 1 && problem[y+1][x] == color )
  142.             markDfs( x, y+1, color );
  143.         if ( x > 0 && problem[y][x-1] == color )
  144.             markDfs( x-1, y, color );
  145.         if ( y > 0 && problem[y-1][x] == color )
  146.             markDfs( x, y-1, color );
  147.     }
  148.  
  149.     void successor( int color, BoardState &out ) const {
  150.         out = *this;
  151.  
  152.         for ( int y = 0; y < problemSize; ++y ) {            
  153.             for ( int x = 0; x < problemSize; ++x ) {
  154.                 if ( problem[y][x] == color && !present( x, y ) ) {
  155.                     if ( adjacentPresent( x, y ) ) {
  156.                         out.markDfs( x, y, color );
  157.                     }
  158.                 }
  159.             }
  160.         }
  161.     }
  162.    
  163.     bool possiblePredecessorTo( const BoardState &other ) const {
  164.         // Can this state evolve into 'other'?
  165.         // We should have no '1's where the other state has '0's.
  166.         // 0 0 -- True
  167.         // 1 0 -- False
  168.         // 0 1 -- True
  169.         // 1 1 -- True
  170.         for ( int b = 0; b < boardSize; ++b ) {
  171.             uint8_t tmp = bits_[b] & ~( other.bits_[b] );
  172.             if ( tmp ) return false;
  173.         }
  174.         return true;
  175.     }
  176.  
  177.     uint64_t hashValue( void ) const {
  178.         uint64_t key = 0;
  179.         for ( int i = 0; i < boardSize; ++i ) {
  180.             key = ( key << 8 ) ^ ( key >> 56 ) ^ bits_[i];
  181.         }
  182.  
  183.         // Those keys are *decidedly* nonrandom, use a mixing function to
  184.         // distribute what entropy exists.
  185.         // copied from http://www.concentric.net/~ttwang/tech/inthash.htm
  186.         key = (~key) + (key << 21); // key = (key << 21) - key - 1;
  187.         key = key ^ ( (key >> 24) | ( key >> 40 ) ) ; // key >>> 24
  188.         key = (key + (key << 3)) + (key << 8); // key * 265
  189.         key = key ^ ( (key >> 14) | ( key >> 50 ) ); // key >>> 14
  190.         key = (key + (key << 2)) + (key << 4); // key * 21
  191.         key = key ^ ( (key >> 28) | ( key >> 38 ) ); // key >>> 28;
  192.         key = key + (key << 31);
  193.         return key;
  194.     }
  195.  
  196.     void print( std::ostream &out ) const {
  197.         for ( int y = 0; y < problemSize; ++y ) {            
  198.             for ( int x = 0; x < problemSize; ++x ) {
  199.                 if ( present( x, y ) ) out << "X";
  200.                 else out << ".";
  201.             }
  202.             out << std::endl;
  203.         }
  204.         out << "Squares left:";
  205.         for ( int c = 0; c < numColors; ++c ) {
  206.             out << " " << (uint16_t)colorsLeft_[c];
  207.         }
  208.         out << std::endl;
  209.     }
  210.  
  211.     void printWithProblem( std::ostream &out ) const {
  212.         for ( int y = 0; y < problemSize; ++y ) {            
  213.             for ( int x = 0; x < problemSize; ++x ) {
  214.                 if ( present( x, y ) ) out << '*';
  215.                 else out << colorTranslation[ problem[y][x] ];
  216.             }
  217.             out << std::endl;
  218.         }
  219.         out << std::endl;
  220.     }
  221. } __attribute__(( packed ));
  222.  
  223. const BoardState BoardState::emptyExample;
  224. const BoardState BoardState::goalState = true;
  225.  
  226. class BoardHashTable {
  227. public:
  228.     int numEntries_;
  229.     int numUsed_;
  230.     int depth_;
  231.     int numCollisions_;
  232.  
  233.     BoardState *table_;
  234.    
  235.     BoardHashTable( int size, int depth = 0 ) {
  236.         table_ = (BoardState *)calloc( size, sizeof( BoardState ) );
  237.         numUsed_ = 0;
  238.         numEntries_ = size;
  239.         depth_ = depth;
  240.         numCollisions_ = 0;
  241.     }
  242.  
  243.     ~BoardHashTable() {        
  244.         free( table_ );
  245.     }
  246.  
  247.     BoardState & bucket( const BoardState &s ) const {
  248.         uint64_t offset = s.hashValue() % numEntries_;
  249.         // std::cout << "hash " << s.hashValue() << " bucket " << offset << std::endl;
  250.         // Quick and dirty linear probing.
  251.         // FIXME: use quadratic instead?
  252.         while ( !table_[offset].empty() && table_[offset] != s ) {
  253.             offset += 1;
  254.             offset %= numEntries_;
  255.             const_cast<BoardHashTable *>( this )->numCollisions_ += 1;
  256.         }
  257.         return table_[offset];        
  258.     }
  259.  
  260.     void insert( const BoardState &s ) {
  261.         BoardState &b = bucket( s );
  262.         if ( b.empty() ) {
  263.             numUsed_ += 1;
  264.             b = s;
  265.         }
  266.     }
  267.  
  268.     bool present( const BoardState &s ) const {
  269.         BoardState &b = bucket( s );
  270.         return !b.empty();
  271.     }
  272. };
  273.  
  274. uint64_t pruneHit = 0;
  275. uint64_t pruneLookup = 0;
  276. uint64_t historyHit = 0;
  277. uint64_t historyLookup = 0;
  278. uint64_t colorPrune = 0;
  279. uint64_t colorLookup = 0;
  280.  
  281. void resetStats( void ) {
  282.     pruneHit = 0;
  283.     pruneLookup = 0;
  284.     historyHit = 0;
  285.     historyLookup = 0;
  286.     colorPrune = 0;
  287.     colorLookup = 0;
  288. }
  289.  
  290.  
  291. BoardHashTable *
  292. nextDepth( BoardHashTable *prev,
  293.            int depth,
  294.            bool log,
  295.            BoardHashTable *extraPrune,
  296.            int depthBound ) {
  297.     if ( log ) {
  298.         std::cout << "===== Depth " << depth << " =====" << std::endl;
  299.     }
  300.  
  301.     const int maxSize = 20000000;
  302.     int estimate =  5 * prev->numUsed_ * 10 / 6 + 6;
  303.     int prune = 0;
  304.     int prune2 = 0;
  305.     BoardHashTable *next =
  306.         new BoardHashTable( std::min( estimate, maxSize ), depth );
  307.     if ( log ) {
  308.         std::cout << "Table size: " << next->numEntries_ << std::endl;
  309.     }
  310.     bool colorBoundActive = ( depthBound - depth <= 6 );
  311.    
  312.     for ( int i = 0; i < prev->numEntries_; ++i ) {
  313.         BoardState &s = prev->table_[ i ];
  314.         if ( !s.empty() ) {
  315.             for ( int c = 0; c < numColors; ++c ) {
  316.                 BoardState successor;
  317.                 s.successor( c, successor );
  318.                
  319.                 if ( successor != s ) { // valid move
  320.                    
  321.                     if ( colorBoundActive ) {
  322.                         colorLookup += 1;
  323.                         if ( depth + successor.numColorsLeft() >= depthBound ) {
  324.                             colorPrune += 1;
  325.                             continue;
  326.                         }
  327.                     }
  328.  
  329.                     pruneLookup += 1;
  330.                     if ( extraPrune ) historyLookup += 1;
  331.  
  332.                     if ( prev->present( successor ) ) {
  333.                         prune += 1;
  334.                         pruneHit += 1;
  335.                     } else if ( extraPrune &&
  336.                                 extraPrune->present( successor ) ) {
  337.                         prune += 1;
  338.                         prune2 += 1;
  339.                         historyHit += 1;
  340.                     } else {
  341.                         next->insert( successor );
  342.                     }
  343.                     /*
  344.                       std::cout << "Color " << c << " successor: " << std::endl;
  345.                       successor.print( std::cout );
  346.                     */
  347.                 }
  348.             }
  349.         }
  350.         assert( next->numUsed_ <= next->numEntries_ * 8 / 10 );
  351.     }
  352.     if ( log ) {
  353.         std::cout << "Found " << next->numUsed_ << " successor states."
  354.                   << std::endl
  355.                   << next->numCollisions_ << " hash table collisions."
  356.                   << std::endl
  357.                   << prune << " dominated paths pruned." << std::endl;
  358.         if ( extraPrune && prune2 > 0 ) {
  359.             std::cout << "(" << prune2 << " from extra hints.)" << std::endl;
  360.         }
  361.     }
  362.     return next;
  363. }
  364.  
  365. bool
  366. leafSearch( BoardHashTable *prev ) {
  367.     for ( int i = 0; i < prev->numEntries_; ++i ) {
  368.         BoardState &s = prev->table_[ i ];
  369.         if ( !s.empty() ) {
  370.             for ( int c = 0; c < numColors; ++c ) {
  371.                 BoardState successor;
  372.                 s.successor( c, successor );
  373.                
  374.                 if ( successor == BoardState::goalState ) return true;
  375.             }
  376.         }
  377.     }
  378.     return false;
  379. }
  380.  
  381. uint64_t successorsGenerated = 0;
  382. uint64_t prunedByIntermediate = 0;
  383.  
  384. bool
  385. dfs( std::vector<int> &solution,
  386.      const BoardState &begin,
  387.      const BoardState &goal,
  388.      int maxDepth,
  389.      bool checkIntermediateAgainstGoal,
  390.      int lastColor = -1 ) {
  391.     for ( int c = 0; c < numColors; ++c ) {
  392.         if ( c == lastColor ) continue;
  393.         if ( begin.colorsLeft_[c] == 0 ) continue;
  394.  
  395.         BoardState s;        
  396.         begin.successor( c, s );
  397.         successorsGenerated += 1;
  398.         if ( s == begin ) continue;
  399.  
  400.         if ( s == goal ) {
  401.             solution.push_back( c );
  402.             return true;
  403.         }
  404.         if ( maxDepth == 1 ) continue;
  405.  
  406.         if ( checkIntermediateAgainstGoal &&
  407.              !s.possiblePredecessorTo( goal ) ) {
  408.             prunedByIntermediate += 1;
  409.             continue;
  410.         }
  411.  
  412.         bool succeed = dfs( solution,
  413.                             s, goal, maxDepth - 1,
  414.                             checkIntermediateAgainstGoal,
  415.                             c );
  416.         if ( succeed ) {
  417.             solution.push_back( c );
  418.             return true;
  419.         }
  420.     }
  421.     return false;
  422. }
  423.  
  424. BoardState
  425. showSolution( std::vector<int> &solution,
  426.               int &move,
  427.               const BoardState &start ) {
  428.     std::reverse( solution.begin(), solution.end() );
  429.     BoardState curr = start;
  430.     for ( std::vector<int>::iterator i = solution.begin();
  431.           i != solution.end(); ++i ) {
  432.         std::cout << "move " << ++move
  433.                   << " pick " << colorTranslation[*i] << std::endl;
  434.         BoardState nextBoard;
  435.         curr.successor( *i, nextBoard );
  436.         nextBoard.printWithProblem( std::cout );
  437.         curr = nextBoard;
  438.     }
  439.  
  440.     return curr;
  441. }
  442.  
  443. void
  444. readProblem( const char *filename ) {
  445.     std::ifstream in( filename );
  446.     std::map< char, int > translate;
  447.     int nextColor = 0;
  448.  
  449.     for ( int c = 0; c < numColors; ++c ) {
  450.         colorCount[c] = 0;
  451.     }
  452.     for ( int y = 0; y < problemSize; ++y ) {
  453.         problem[y] = new int[problemSize];
  454.         for ( int x = 0; x < problemSize; ++x ) {
  455.             char c = in.get();
  456.             int color;
  457.             if ( c >= '0' && c <= '9' ) {
  458.                 color = c - '0';
  459.             } else {
  460.                 std::map<char, int>::iterator i = translate.find( c );
  461.                 if ( i == translate.end() ) {
  462.                     color = nextColor;
  463.                     translate.insert( std::make_pair( c, color ) );
  464.                     colorTranslation[color] = c;
  465.                     nextColor += 1;
  466.                 } else {
  467.                     color = i->second;
  468.                 }
  469.             }
  470.             problem[y][x] = color;
  471.             colorCount[color] += 1;
  472.         }
  473.         in >> std::ws;
  474.     }
  475. }
  476.  
  477. int main( int argc, char *argv[] ) {
  478.     int midpointDepth = 13;
  479.  
  480.     if ( argc == 1 ) {
  481.         randomProblem();
  482.     }
  483.     if ( argc >= 2 ) {
  484.         midpointDepth = atoi( argv[1] );
  485.     }
  486.     if ( argc >= 3 ) {
  487.         readProblem( argv[2] );
  488.     }
  489.  
  490.     std::cout << "====== Problem instance =====" << std::endl;
  491.     for ( int y = 0; y < problemSize; ++y ) {
  492.         for ( int x = 0; x < problemSize; ++x ) {
  493.             std::cout << problem[y][x];
  494.         }
  495.         std::cout << std::endl;
  496.     }
  497.    
  498.     BoardHashTable *prev = new BoardHashTable( 3, 0 );
  499.     BoardState start;
  500.     start.markDfs( 0, 0, problem[0][0] );
  501.     prev->insert( start );
  502.     std::cout << std::endl;
  503.     start.print( std::cout );
  504.     std::cout << std::endl;
  505.     BoardState::goalState.print( std::cout );
  506.  
  507.     for ( int depth = 1; depth <= midpointDepth; ++depth ) {        
  508.         BoardHashTable *next = nextDepth( prev, depth, true, 0, 26 );
  509.         delete prev;
  510.         prev = next;        
  511.     }
  512.  
  513.     std::cout << "Hit rate on previous depth " << pruneHit * 1.0 / pruneLookup
  514.               << std::endl;
  515.     resetStats();
  516.  
  517.     std::cout << "===== Subtree search starting from depth " << midpointDepth << " =====" << std::endl;
  518.    
  519.  
  520.     int depthBound = 26;
  521.     BoardState best;
  522.     BoardHashTable *midpoint = prev;
  523.  
  524.     // Cache is only correct if we remember one additional level of depth.
  525.     // It looks like about ~10% hit rate with lookups at depth + 2
  526.     // and 5% hit rate at depth + 3, so +3 looks like a win.
  527.     int maxRemembered = midpointDepth + 1;
  528.     int maxHistoryLookupDepth = midpointDepth + 3;
  529.     BoardHashTable *explored = new BoardHashTable( 20000000, 0 );
  530.     for ( int i = 0; i < midpoint->numEntries_; ++i ) {
  531.         BoardState &s = midpoint->table_[ i ];
  532.         if ( !s.empty() ) {
  533.             explored->insert( s );
  534.         }
  535.     }
  536.    
  537.     int rootCount = 0;
  538.     for ( int i = 0; i < midpoint->numEntries_; ++i ) {
  539.         BoardState &s = midpoint->table_[ i ];
  540.         if ( !s.empty() ) {
  541.             rootCount += 1;
  542.  
  543.             if ( depthBound - midpointDepth <= 6 ) {
  544.                 // color bound active
  545.                 colorLookup += 1;
  546.                 if ( midpointDepth + s.numColorsLeft() >= depthBound ) {
  547.                     colorPrune += 1;
  548.                     continue;
  549.                 }
  550.             }
  551.  
  552.             int nodeCount = 0;
  553.             bool success = false;
  554.             prev = new BoardHashTable( 3, midpointDepth );
  555.             prev->insert( s );
  556.             int depth;
  557.             for ( depth = midpointDepth + 1;
  558.                   depth < depthBound - 1 && prev->numUsed_ > 0;
  559.                   ++depth ) {
  560.                 BoardHashTable *hint = explored;
  561.                 if ( depth > maxHistoryLookupDepth ) {
  562.                     hint = 0;
  563.                 }                
  564.                 BoardHashTable *next =
  565.                     nextDepth( prev, depth,
  566.                                false && rootCount % 1000 == 0,
  567.                                hint,
  568.                                depthBound );
  569.                
  570.                 if ( depth <= maxRemembered ) {
  571.                     if ( explored->numUsed_ + next->numUsed_ <
  572.                          explored->numEntries_ * 8 / 10 ) {
  573.                         if ( false ) {
  574.                             std::cout << "Adding " << next->numUsed_ << " visited entries to remembered history." << std::endl;
  575.                         }
  576.                         for ( int j = 0; j < next->numEntries_; ++j ) {
  577.                             BoardState &s = next->table_[ j ];
  578.                             if ( !s.empty() ) {
  579.                                 explored->insert( s );
  580.                             }
  581.                         }
  582.                        
  583.                     }
  584.                 }
  585.  
  586.                 nodeCount += next->numUsed_;
  587.                 if ( next->present( BoardState::goalState ) ) {
  588.                     depthBound = depth;
  589.                     success = true;
  590.                     best = s;
  591.                 }
  592.                 delete prev;
  593.                 prev = next;
  594.             }
  595.             if ( depth < depthBound && prev->numUsed_ > 0 ) {
  596.                 assert( !success );
  597.                 // No solution found yet.  Don't bother generating a
  598.                 // hash table this time.
  599.                 success = leafSearch( prev );
  600.                 if ( success ) {
  601.                     depthBound = depth;
  602.                     best = s;
  603.                 }
  604.             }
  605.             delete prev;
  606.  
  607.             if ( success ) {
  608.                 std::cout << "Subtree root " << rootCount << " nodecount " << nodeCount << " success " << success << " bound " << depthBound << std::endl;
  609.                 std::cout << "Pruning history size " << explored->numUsed_
  610.                           << " HR prev " << pruneHit * 1.0 / pruneLookup
  611.                           << " HR history " << historyHit * 1.0 / historyLookup
  612.                           << " PR color " << colorPrune * 1.0 / colorLookup
  613.                           << std::endl;
  614.             }
  615.             if ( success ) {
  616.                 s.print( std::cout );
  617.                 resetStats();
  618.             }
  619.         }
  620.     }
  621.    
  622.     std::cout << "===== Subtree search complete =====" << std::endl;
  623.     std::cout << "Pruning history size " << explored->numUsed_
  624.               << " HR prev " << pruneHit * 1.0 / pruneLookup
  625.               << " HR history " << historyHit * 1.0 / historyLookup
  626.               << " PR color " << colorPrune * 1.0 / colorLookup
  627.               << std::endl;
  628.     std::cout << "Color prune checks: " << colorLookup << std::endl;
  629.  
  630.     std::cout << "===== Searching for path to midpoint =====" << std::endl;
  631.     std::vector<int> solution;
  632.     assert( dfs( solution, start, best, midpointDepth, true ) );
  633.  
  634.     std::cout << successorsGenerated << " nodes generated, "
  635.               << prunedByIntermediate << " pruned by comparison with goal."
  636.               << std::endl;
  637.     successorsGenerated = 0;
  638.  
  639.     int move = 0;
  640.     BoardState afterMoves = showSolution( solution, move, start );
  641.     assert( afterMoves == best );
  642.    
  643.     std::cout << "===== Searching for path to goal =====" << std::endl;
  644.     solution.clear();
  645.     assert( dfs( solution, best, BoardState::goalState,
  646.                  depthBound - midpointDepth, false ) );
  647.     std::cout << successorsGenerated << " nodes generated." << std::endl;
  648.  
  649.     afterMoves = showSolution( solution, move, afterMoves );
  650.     assert( afterMoves == BoardState::goalState );
  651.  
  652.     return 0;
  653. }
  654.  
  655.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement