I first saw this challenge when a user posted it to SO about two years ago without a shred of code.
The post's plea, "How do I get started?" meant the user's post didn't survive long on that site.
This is, it seems, a well-known challenge, with lots of Google-able demonstrations of solutions.
Spoiler Alert: DO NOT go looking for those solutions before considering your interest developing your own version.
The same caution applies to reading/reviewing the working code below.
Once one has "seen it done one way", it's hard to forget and find the joy of one's own achievement.
The challenge
Scene: 8 people are standing together on a river bank; 2 parents, 2 daughters, 2 sons, 1 cop and 1 criminal. The challenge is to orchestrate them all crossing the river using one small boat capable of carrying only two individuals, maximum.
Goal: all 8 are on the opposite river bank at the end.
Constraints:
- The criminal cannot be in the company of any family member unless the cop is present.
- The mother cannot be in the company of either son unless dad is present.
- The dad cannot be in the company of either daughter unless mom is present.
- Only the cop, mom and dad are able to row the boat (no kid, no criminal).
Find the most efficient solution(s) so that all 8 are able to continue their journey having crossed the river.
One programmatic solution
This is yet another Windows Console App with a focus on coding an efficient algorithmic solution, and minimal attention given to "looking nice."
This is a 'single source' app that uses a few file scope variables to cut-down on passing parameters to functions.
#include <stdio.h> #include <stdint.h> #include <stdarg.h> int trace = 0; // threshold 0-7 for reporting something void Trace( int lvl, char *fmt, ... ) { if( lvl > trace ) return; va_list ap; va_start(ap, fmt); vfprintf( stderr, fmt, ap ); va_end( ap ); } enum { // the cast of 2 daughters, 2 sons, 1 bad guy, 1 good guy, and 2 parents (each a bit) eD1=1, eD2=2, eS1=4, eS2=8, eBG=16, eGG=32, eMo=64, eDa=128, eDD = eD1 | eD2, // 2 daughters eSS = eS1 | eS2, // 2 sons eMD = eMo | eDa, // parents eFM = eDD | eSS |eMo | eDa // family }; // give 'em names char *who[] = { "Dot", "Dee", "Sam", "Sem", "Crm", "Cop", "Mum", "Dad" }; // Show a single "population" as names. Could be those in the boat. void showPop( char *ttl, uint8_t sel ) { printf( ttl ); for( uint8_t msk = 0x80, i = 7; msk; msk >>= 1, i-- ) printf( "%-4s", ( sel & msk ) ? who[i] : "" ); } // Show populations on both banks void showState( uint8_t b0, uint8_t b1 ) { showPop( "State: ", b0 ); showPop( " ==== ", b1 ); putchar( '\n' ); } uint32_t bitpop[ 8 ]; // 8 bits on one bank => 256 populations; most are invalid. uint16_t pops[ 100 ]; // permitted populations empirically found to be 70, in fact! int nPops; uint16_t bhb4[ 100 ]; // "Been Here BeFore" to prevent infinite cycles // precalculate bit patterns of permitted populations bool BADq( uint8_t b ) { return (b & eDD) && (b & eSS) && !(b & eMD); } // No kids w/o at least one parent bool BADx( uint8_t b ) { return (b & eBG) && !(b & eGG) && (b & eFM); } // no thief w/o policeman if family present bool BADy( uint8_t b ) { return (b & eDa) && !(b & eMo) && (b & eDD); } // no dad w/o mom with a daughter bool BADz( uint8_t b ) { return (b & eMo) && !(b & eDa) && (b & eSS); } // no mom w/o dad with a son void mkPops() { for( int i = 0; i <= 0xFF; i++ ) { uint8_t l = (uint8_t)~i; // left... uint8_t r = (uint8_t)~l; // right river bank if( BADq( l ) || BADq( r ) // reject those that are bad || BADx( l ) || BADx( r ) || BADy( l ) || BADy( r ) || BADz( l ) || BADz( r ) ) continue; pops[ nPops++ ] = (uint16_t)(l<<8 | r); // record good pops for both sides as 16 bits bitpop[ (i >> 5) & 7 ] |= ( 1 << ( i & 0x1F ) ); } } // precalculate permitted boat occupants uint8_t trav[ 3 * 8 ]; // 3 rowers with 8 passengers (including self twice!) int nTrav; // actual count without duplicates void mkTrav() { // fill array of possible boat occupant(s) trav[ nTrav++ ] = eDa; // first the rowers (altogether) as solo (for return trips) trav[ nTrav++ ] = eMo; trav[ nTrav++ ] = eGG; for( uint8_t p0 = eDa; p0 >= eGG; p0 >>= 1 ) for( uint8_t p1 = eDa; p1; p1 >>= 1 ) { if( ( p0 == p1 ) // already have solos [0], [1] and [2] || ( p0 == eDa && (p1 & eDD) ) // dad cannot cross with one daughter || ( p0 == eMo && (p1 & eSS) ) // mom cannot cross with one son || ( p1 == eBG && !(p0 &eGG) ) ) // crim can only cross with cop continue; trav[ nTrav ] = (uint8_t)(p0 | p1); // make a pair of travellers for( int i = 0; i < nTrav && trav[ nTrav ] != trav[ i ]; i++ ) {} // no duplicates nTrav += i == nTrav; // keep this pairing in array } if( trace >= 7 ) { printf( "\ntrav cnt %d\n", nTrav ); for( int i = 0; i < nTrav; i++ ) { printf( "%2d: ", i ); showPop( "Boat pair: ", trav[ i ] ); putchar( '\n' ); } } } // recursive search for next step in sequence int solve( int ply, uint16_t pop ) { int res = 0; bhb4[ ply-1 ] = pop; uint8_t from = (uint8_t)( !(ply%2) ? pop : pop>>8 ); uint8_t dest = (uint8_t)( (ply%2) ? pop : pop>>8 ); Trace( 2, ">> Solve( %d ) %04X (%02X) (%02X)\n", ply, pop, from, dest ); for( int i, tb = (ply%2) ? 3 : 0; tb < nTrav; tb++ ) { Trace( 5, "%2d:%2d: From %02X, Travellers %02X\n", ply, tb, from, trav[tb] ); if( (from & trav[tb]) != trav[tb] ) { Trace( 6, "travellers not present\n" ); continue; } // use temp copies to evaluate this transer of persons uint16_t tmpFrom = (uint16_t)(from & ~trav[tb]); uint16_t tmpDest = (uint16_t)(dest | trav[tb]); uint16_t testPop = (uint16_t)((ply%2) ? (tmpFrom<<8) | tmpDest : tmpFrom | (tmpDest<<8) ); if( trace >= 10 ) { printf( "Considering: " ); showState( (uint8_t)(testPop>>8), (uint8_t)testPop ); getchar(); } if( ( bitpop[ ( testPop >> 5) & 7 ] & ( 1 << ( testPop & 0x1F ) ) ) == 0 ) { Trace( 2, "not a valid population\n" ); continue; } for( i = 0; i < ply && ( testPop != bhb4[i] || (ply%2) != (i%2) ); i++ ) ; // loop if( i < ply ) { Trace( 2, "Been here before. Curr %d Prev %d\n", ply, i+1 ); continue; } if( testPop == 0x00FF ) { // base case puts( "\n FOUND ONE \n" ); bhb4[ ply++ ] = testPop; for( i = 0; i < ply; i++ ) showState( (uint8_t)(bhb4[i]>>8), (uint8_t)bhb4[i] ); for( int i = 0; i < ply; i++ ) printf( "%04X ", bhb4[i] ); getchar(); Trace( 2, "*** BACKTRACKING ***\n" ); ply--; bhb4[ ply-1 ] = 0; return 1; } res += solve( ply+1, testPop ); } Trace( 2, "*** BACKTRACKING ***\n" ); bhb4[ ply-1 ] = 0; return res; } int main( void ) { mkPops(); mkTrav(); int nSol = solve( 1, 0xFF00 ); // Everyone on the first bank printf( "%d solutions found\n", nSol ); for( int i = 0; i < nPops; i++ ) { printf( "%2d: %04X\t", i, pops[i] ); showState( (uint8_t)(pops[i]>>8), (uint8_t)pops[i] ); } for( i = 0; i < 8; i++ ) printf( "%08X\n", bitpop[i] ); return 0; } Output of one solution
FOUND ONE State: Dad Mum Cop Crm Sem Sam Dee Dot ==== State: Dad Mum Sem Sam Dee Dot ==== Cop Crm State: Dad Mum Cop Sem Sam Dee Dot ==== Crm State: Dad Mum Sam Dee Dot ==== Cop Crm Sem State: Dad Mum Cop Crm Sam Dee Dot ==== Sem State: Mum Cop Crm Dee Dot ==== Dad Sem Sam State: Dad Mum Cop Crm Dee Dot ==== Sem Sam State: Cop Crm Dee Dot ==== Dad Mum Sem Sam State: Mum Cop Crm Dee Dot ==== Dad Sem Sam State: Mum Dee Dot ==== Dad Cop Crm Sem Sam State: Dad Mum Dee Dot ==== Cop Crm Sem Sam State: Dee Dot ==== Dad Mum Cop Crm Sem Sam State: Mum Dee Dot ==== Dad Cop Crm Sem Sam State: Dot ==== Dad Mum Cop Crm Sem Sam Dee State: Cop Crm Dot ==== Dad Mum Sem Sam Dee State: Crm ==== Dad Mum Cop Sem Sam Dee Dot State: Cop Crm ==== Dad Mum Sem Sam Dee Dot State: ==== Dad Mum Cop Crm Sem Sam Dee Dot FF00 CF30 EF10 C738 F708 738C F30C 33CC 738C 43BC C33C 03FC 43BC 01FE 31CE 10EF 30CF 00FF "Tower of Hanoi" with additional complications to make it interesting.
How many different solutions can you find?
Review objectives:
Any/all comments on my code solution (esp. on the theme of recursion) are welcome.
EDIT:
@TobySpeight correctly points out, below, that the code presented in this question does not "pass muster" when presented to a modern C compiler. Mea culpa. I do NOT wish to invalidate that answer.
Readers interested in this coding challenge and one solution, can find, here at godbolt.org, the code rectified to compile cleanly with gcc 14.1.
Dusting it off (it's been a couple of years) and looking at it again after seeing to its deficiencies, a few more minimal changes have been made to improve its output, and to touch-up a few operations. "Differencing" the two versions may be interesting to readers.
In a large codebase maintained by several coders, engaging all available "proofreading review" options of a modern compiler is certainly a recommended practise. Thank you @Toby.
int ivariables, andinclude <stdbool.h>to shift from C++ to C... In this instance, if there's no "I" in "team", then the "team" has 0 members... Makes "going out for a beer after work kinda difficult.":-)Point noted, thank you... Cheers!:-)\$\endgroup\$std=c23and you getbool,true, andfalsefor free. ;) \$\endgroup\$:-)\$\endgroup\$