[Wellington-pm] SEND + MORE = MONEY

Douglas Bagnall douglas at paradise.net.nz
Thu Mar 3 14:19:59 PST 2005



Yesterday, I had a go with a hybrid stochastic/ combinatorial approach. 
  I reasoned (or rather, imagined) that when the error is low, the most 
significant digits would be settled, and permutations of the less 
significant digits would quickly find the solution.  This was not true. 
  While M and O are easily fixed, S could be any manner of things with 
the sum out by only one.

So I gave that up, but seeing that there were these local minima,  I 
imagined again that the evolutionary process was occasionally getting 
stuck in them, thus not coming up with an answer in any given number of 
cycles.  I borrowed from the natural world and introduced periodic 
cataclysmic events, so if progress is arbitrarily slow, the entire pool 
gets jumbled.  It seems to work -- it hasn't been stuck since, over 
thousands of cycles.

On an Athlon XP 2800+, it takes about 0.2 seconds per solution -- the 
script solves it 100 times to get a reasonable average.

$ time ./sum4.pl
found '9567 + 1085 = 10652' (SONYEMDR => 90625178), 100 times

real    0m17.634s


For questions with more than one solution, it tends to find them all. 
For example, Srdjan's sum has 2 solutions:

$ ./sum4.pl 'SEND + ME + MORE = MONEY'
found '9458 + 14 + 1074 = 10546' (SONYEMDR => 90564187), 52 times
found '9346 + 13 + 1073 = 10432' (SONYEMDR => 90423167), 48 times



douglas
























#!/usr/bin/perl -w

use strict;
my $message = shift || 'SEND + MORE = MONEY';
my $GOES = shift || 100;
my $ITERATIONS = shift || 2000;
my $POOL = shift || 100;
my $CUTOFF = shift || 34;
my $CATACLYSMS = shift || 400;
my $INF = 1e999;
my $MAXMAG = 12; #maximum word length.

my @letters = keys %{{ map { $_ => 1} ($message =~ /([A-Z])/g)}};
my $letters = join('', @letters);
my $len  = @letters;

my $equation = $message;
$equation =~ s/=/-/;  # should check for bad chars,
                       # put () around each side.

my %answers;
my $noanswer = 0;

OUTER: foreach(1..$GOES){
     my @nums = (0..9);
     my %candidates = map {
	for (my $i = 10; --$i;) {
	    my $r = int(rand($i + 1));
	    ($nums[$i], $nums[$r]) = ($nums[$r], $nums[$i]);
	};
	substr(join('', @nums), 0, $len) => $INF;
     }(1..$POOL);

     foreach my $i (1..$ITERATIONS){
	my @scores = sort {$a <=> $b} (values(%candidates));
	my $cutoff = $scores[$CUTOFF];
	my %new_candidates;
	while (my ($digits, $score) = each %candidates) {
	    if ($score eq $INF){
		$_ = $equation;
		eval "tr/$letters/$digits/";
		next if(/\b0/);
		$score = abs (eval $_);
		next if ($score > $cutoff);
		if ($score == 0){
		    s/-/=/;
		    #print "found $digits in $i iterations\n";
		    $answers{$digits} += 1;
		    next OUTER;
		}
	    }
	    $new_candidates{$digits} = $score if ($score <= $cutoff);
	}
	%candidates = %new_candidates;
	my @mutatees = keys(%candidates);
	my $c = @mutatees;
	if ($i % $CATACLYSMS){
	    while ($c < $POOL){
		foreach my $digits (@mutatees){
		    my $pos = int(rand($len));
		    my $new_n = int(rand(10));
		    my $old_n = substr($digits, $pos, 1, 'x');
		    $digits =~ s/$new_n/$old_n/;
		    $digits =~ s/x/$new_n/;
		    $candidates{$digits} = $INF;
		    last if (++$c >= $POOL);
		}
	    }
	}
	else{
	    # damage all answers.
	    %candidates = ();
	    $c = 0;
	    #print " * cataclysm after $i cycles\n";
	    while ($c < $POOL){
		foreach my $digits (@mutatees){
		    foreach(1..3){
			my $pos = int(rand($len));
			my $new_n = int(rand(10));
			my $old_n = substr($digits, $pos, 1, 'x');
			$digits =~ s/$new_n/$old_n/;
			$digits =~ s/x/$new_n/;
		    }
		    $candidates{$digits} = $INF;
		    last if (++$c >= $POOL);		
		}
	    }
	}
     }
     print "No solution found after $ITERATIONS iterations!\n";
     $noanswer += 1;
}


while(my ($digits, $count) = each(%answers)){
     $_ = $message;
     eval "tr/$letters/$digits/";
     print "found '$_' ($letters => $digits), $count times\n";
}





More information about the Wellington-pm mailing list