# \[QUIZ\] Verbal Arithmetic (#128)

**URL:** <https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379>\
**Category:** ruby-talk\
**Created:** [15 June 2007 12:23 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379 "2007-06-15T12:23:44Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![James\_Edward\_Gray\_II](https://avatars.discourse-cdn.com/v4/letter/j/ea5d25/32.png) [@James\_Edward\_Gray\_II](https://rubytalk.org/u/James_Edward_Gray_II)\
**Post date:** [15 June 2007 12:23 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/1 "2007-06-15T12:23:44Z")

</div>

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until  
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

[http://www.rubyquiz.com/](http://www.rubyquiz.com/)

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone  
on Ruby Talk follow the discussion. Please reply to the original quiz message,  
if you can.

> **···**
>
> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
> 
> A famous set of computer problems involve verbal arithmetic. In these problems,  
> you are given equations of words like:
> 
> &nbsp;&nbsp;&nbsp;&nbsp;send  
> &nbsp;&nbsp;+ more  
> &nbsp;&nbsp;------  
> &nbsp;&nbsp;&nbsp;money
> 
> or:
> 
> &nbsp;&nbsp;&nbsp;&nbsp;forty  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ten  
> &nbsp;&nbsp;+ ten  
> &nbsp;&nbsp;-------  
> &nbsp;&nbsp;&nbsp;&nbsp;sixty
> 
> The goal is to find a single digit for each letter that makes the equation true.  
> Normal rules of number construction apply, so the first digit of a multi-digit  
> number should be nonzero and each letter represents a different digit.
> 
> This week's quiz is to build a program that reads in equations and outputs  
> solutions. You can decide how complex of an equation you want to support, with  
> the examples above being the minimum implementation.
> 
> Here's a solution you can test against:
> 
> &nbsp;&nbsp;$ ruby verbal\_arithmetic.rb 'send+more=money'  
> &nbsp;&nbsp;s: 9  
> &nbsp;&nbsp;e: 5  
> &nbsp;&nbsp;n: 6  
> &nbsp;&nbsp;d: 7  
> &nbsp;&nbsp;m: 1  
> &nbsp;&nbsp;o: 0  
> &nbsp;&nbsp;r: 8  
> &nbsp;&nbsp;y: 2

---

<div class="post-metadata">

**Author:** ![anansi](https://avatars.discourse-cdn.com/v4/letter/a/9fc348/32.png) [@anansi](https://rubytalk.org/u/anansi)\
**Post date:** [15 June 2007 12:55 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/2 "2007-06-15T12:55:05Z")

</div>

I really don't get it 🙂 How did you figured out that 2 = y in your example?

> **···**
>
> > Here's a solution you can test against:
> > 
> > &nbsp;&nbsp;$ ruby verbal\_arithmetic.rb 'send+more=money'  
> > &nbsp;&nbsp;s: 9  
> > &nbsp;&nbsp;e: 5  
> > &nbsp;&nbsp;n: 6  
> > &nbsp;&nbsp;d: 7  
> > &nbsp;&nbsp;m: 1  
> > &nbsp;&nbsp;o: 0  
> > &nbsp;&nbsp;r: 8  
> > &nbsp;&nbsp;y: 2
> 
> --  
> greets
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;one must still have chaos in oneself to be able to give birth to a dancing star

---

<div class="post-metadata">

**Author:** ![Aureliano\_Calvo](https://avatars.discourse-cdn.com/v4/letter/a/f04885/32.png) [@Aureliano\_Calvo](https://rubytalk.org/u/Aureliano_Calvo)\
**Post date:** [16 June 2007 23:41 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/3 "2007-06-16T23:41:52Z")

</div>

Mi implementation is "working". For instance:

./quiz\_128.rb "a+b=c"  
b: 1  
c: 6

But  
./quiz\_128.rb "send+more=money" is taking ages!

time ./quiz\_128.rb "send+more=money"  
m: 1  
y: 2  
n: 6  
o: 0  
d: 7  
e: 5  
r: 8  
s: 9

real 7m17.065s  
user 2m46.806s  
sys 0m6.676s

Is there some trick to cut the solution space (I'm just scanning the  
solution space) ? I know I have a crappy PC but I have a feeling that  
this is not the real problem! When can I send my solution to be  
reviewed?

> **···**
>
> a: 5
> 
> > The three rules of Ruby Quiz:
> > 
> > 1. Please do not post any solutions or spoiler discussion for this quiz until  
> > 48 hours have passed from the time on this message.
> > 
> > 2. Support Ruby Quiz by submitting ideas as often as you can:
> > 
> > [http://www.rubyquiz.com/](http://www.rubyquiz.com/)
> > 
> > 3. Enjoy!
> > 
> > Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone  
> > on Ruby Talk follow the discussion. Please reply to the original quiz message,  
> > if you can.
> > 
> > -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
> > 
> > A famous set of computer problems involve verbal arithmetic. In these problems,  
> > you are given equations of words like:
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ more  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;------  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;money
> > 
> > or:
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;forty  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ten  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ ten  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-------  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sixty
> > 
> > The goal is to find a single digit for each letter that makes the equation true.  
> > Normal rules of number construction apply, so the first digit of a multi-digit  
> > number should be nonzero and each letter represents a different digit.
> > 
> > This week's quiz is to build a program that reads in equations and outputs  
> > solutions. You can decide how complex of an equation you want to support, with  
> > the examples above being the minimum implementation.
> > 
> > Here's a solution you can test against:
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$ ruby verbal\_arithmetic.rb 'send+more=money'  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s: 9  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e: 5  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;n: 6  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d: 7  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m: 1  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;o: 0  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r: 8  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y: 2
> 
> --  
> "Es también nuestra intención erradicar la corrupción, ofreciendo como  
> norma la honestidad, la idoneidad y la eficiencia. Con madurez y  
> sentido de unidad es fácil pensar en la recomposición del ser  
> argentino. Ese ser argentino, basado en madurez y en sentido de  
> unidad, permitirá inspirar para elevarnos por encima de la miseria que  
> la antinomia nos ha planteado, para dejar, de una vez por todas, ese  
> ser "anti" y ser, de una vez por todas, "pro": "Pro argentinos""
> 
> Jorge Rafael Videla para el 25 de mayo de 1976

---

<div class="post-metadata">

**Author:** ![Raf\_Coremans](https://avatars.discourse-cdn.com/v4/letter/r/9dc877/32.png) [@Raf\_Coremans](https://rubytalk.org/u/Raf_Coremans)\
**Post date:** [17 June 2007 14:03 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/4 "2007-06-17T14:03:24Z")

</div>

Hi,

Here's my solution: [http://pastie.caboo.se/71188](http://pastie.caboo.se/71188)

It's of the dumb-brute-force-slow-as-hell variety:

$ time ./rq128\_verbalarithmetic\_rafc.rb 'send + more = money'  
{"m"=\>1, "y"=\>2, "n"=\>6, "o"=\>0, "d"=\>7, "e"=\>5, "r"=\>8, "s"=\>9}

real 2m51.197s  
user 2m39.722s  
sys 0m11.397s

$ time ./rq128\_verbalarithmetic\_rafc.rb 'forty + ten + ten = sixty'  
{"x"=\>4, "n"=\>0, "y"=\>6, "o"=\>9, "e"=\>5, "f"=\>2, "r"=\>7, "s"=\>3, "i"=\>1,  
"t"=\>8}

real 7m28.151s  
user 6m55.114s  
sys 0m32.938s

But on the other hand, it can handle any operator as well as bases other  
than 10. Did you know for instance that in base 9, ruby is worth up to 4  
perls with some extra fun thrown in?

$ ./rq128\_verbalarithmetic\_rafc.rb 'n \* perl + fun = ruby' 9  
{"l"=\>8, "b"=\>7, "y"=\>0, "n"=\>1, "e"=\>5, "p"=\>3, "f"=\>6, "r"=\>4, "u"=\>2}  
{"l"=\>8, "b"=\>7, "y"=\>0, "n"=\>1, "e"=\>6, "p"=\>3, "f"=\>5, "r"=\>4, "u"=\>2}  
{"l"=\>1, "b"=\>7, "y"=\>4, "n"=\>2, "e"=\>6, "p"=\>3, "f"=\>5, "r"=\>8, "u"=\>0}  
{"l"=\>2, "b"=\>5, "y"=\>0, "n"=\>3, "e"=\>7, "p"=\>1, "f"=\>8, "r"=\>6, "u"=\>4}  
{"l"=\>4, "b"=\>5, "y"=\>6, "n"=\>3, "e"=\>0, "p"=\>2, "f"=\>8, "r"=\>7, "u"=\>1}  
{"l"=\>4, "b"=\>0, "y"=\>6, "n"=\>3, "e"=\>1, "p"=\>2, "f"=\>8, "r"=\>7, "u"=\>5}  
{"l"=\>4, "b"=\>7, "y"=\>6, "n"=\>3, "e"=\>5, "p"=\>2, "f"=\>1, "r"=\>8, "u"=\>0}  
{"l"=\>2, "b"=\>6, "y"=\>3, "n"=\>4, "e"=\>7, "p"=\>1, "f"=\>5, "r"=\>8, "u"=\>0}

Regards,  
Raf

---

<div class="post-metadata">

**Author:** ![Jesse\_Merriman](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@Jesse\_Merriman](https://rubytalk.org/u/Jesse_Merriman)\
**Post date:** [17 June 2007 17:25 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/5 "2007-06-17T17:25:49Z")

</div>

My solution works with addition and multiplication of any sized list of words,  
and subtraction of a list of exactly two words. Its also a fair bit faster  
than brute force. Unfortunately it got pretty messy, and after some rough  
debugging I'm not in the mood to clean it up just now.

I came up with the basic idea by noticing that the equations can be built  
up and checked from simpler equations taken from the right-hand side. Er..  
lemme give an example to explain better:

&nbsp;&nbsp;9567  
+ 1085

[perms\_and\_combs.rb](https://rubytalk.org/uploads/short-url/dVD6cXRWwbYX9eM3DanYAjPBY2W.rb) (2.01 KB)

[verbal\_arithmetic.rb](https://rubytalk.org/uploads/short-url/78115V5YwQSweouRwZEXt8VIHu9.rb) (4.61 KB)

> **···**
>
> ------  
> 10652
> 
> Start off by looking for ways to satisfy:  
> &nbsp;&nbsp;7  
> + 5  
> ---  
> &nbsp;&nbsp;2
> 
> Then move further to the left:
> 
> &nbsp;&nbsp;67  
> + 85  
> ----  
> 152
> 
> The 52 is right, but not the 1. For addition and multiplication, we can just  
> take it mod 100, and if that works then its a possible partial solution.  
> For subtraction of two numbers, though, it doesn't work when it goes negative.  
> The trick in that case is to just mod the left-most digit:
> 
> &nbsp;&nbsp;9567  
> - 1085  
> ------  
> &nbsp;&nbsp;8482
> 
> &nbsp;&nbsp;7  
> - 5  
> ---  
> &nbsp;&nbsp;2 OK
> 
> &nbsp;&nbsp;67  
> - 85  
> ----  
> -22 =\> 82 (-2 mod 10 = 8)
> 
> verbal\_arithmetic.rb contains (old, ugly) code for generating permutations  
> and combinations. So the program works from right-to-left, finding partial  
> solutions that work by going through ways of mapping letters to numbers,  
> and for each one trying to move further left. Basically a depth-first search.
> 
> There are a number of other subteties, but like I said, I'm tired of messing  
> with this now, so I'll leave it there. Ask if you must.
> 
> Examples:
> 
> $ time ./verbal\_arithmetic.rb 'send more' + money  
> Found mapping:  
> &nbsp;&nbsp;m: 1  
> &nbsp;&nbsp;y: 2  
> &nbsp;&nbsp;n: 6  
> &nbsp;&nbsp;o: 0  
> &nbsp;&nbsp;d: 7  
> &nbsp;&nbsp;e: 5  
> &nbsp;&nbsp;r: 8  
> &nbsp;&nbsp;s: 9
> 
> real 0m1.074s  
> user 0m0.993s  
> sys 0m0.019s
> 
> $ ./verbal\_arithmetic.rb 'forty ten ten' + sixty  
> Found mapping:  
> &nbsp;&nbsp;x: 4  
> &nbsp;&nbsp;y: 6  
> &nbsp;&nbsp;n: 0  
> &nbsp;&nbsp;o: 9  
> &nbsp;&nbsp;e: 5  
> &nbsp;&nbsp;f: 2  
> &nbsp;&nbsp;r: 7  
> &nbsp;&nbsp;s: 3  
> &nbsp;&nbsp;t: 8  
> &nbsp;&nbsp;i: 1
> 
> $ ./verbal\_arithmetic.rb 'foo bar' - 'zag'  
> Found mapping:  
> &nbsp;&nbsp;a: 0  
> &nbsp;&nbsp;b: 3  
> &nbsp;&nbsp;z: 4  
> &nbsp;&nbsp;o: 1  
> &nbsp;&nbsp;f: 7  
> &nbsp;&nbsp;r: 9  
> &nbsp;&nbsp;g: 2
> 
> $ ./verbal\_arithmetic.rb 'fo ba' \* 'wag'  
> Found mapping:  
> &nbsp;&nbsp;w: 4  
> &nbsp;&nbsp;a: 2  
> &nbsp;&nbsp;b: 1  
> &nbsp;&nbsp;o: 5  
> &nbsp;&nbsp;f: 3  
> &nbsp;&nbsp;g: 0
> 
> --  
> Jesse Merriman  
> [jessemerriman@warpmail.net](mailto:jessemerriman@warpmail.net)  
> [http://www.jessemerriman.com/](http://www.jessemerriman.com/)

---

<div class="post-metadata">

**Author:** ![Jesse\_Merriman](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@Jesse\_Merriman](https://rubytalk.org/u/Jesse_Merriman)\
**Post date:** [17 June 2007 17:27 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/6 "2007-06-17T17:27:22Z")

</div>

[Note: this didn't seem to get through the first time. Apologies if it did.]

My solution works with addition and multiplication of any sized list of words,  
and subtraction of a list of exactly two words. Its also a fair bit faster  
than brute force. Unfortunately it got pretty messy, and after some rough  
debugging I'm not in the mood to clean it up just now.

I came up with the basic idea by noticing that the equations can be built  
up and checked from simpler equations taken from the right-hand side. Er..  
lemme give an example to explain better:

&nbsp;&nbsp;9567  
+ 1085

[perms\_and\_combs.rb](https://rubytalk.org/uploads/short-url/dVD6cXRWwbYX9eM3DanYAjPBY2W.rb) (2.01 KB)

[verbal\_arithmetic.rb](https://rubytalk.org/uploads/short-url/78115V5YwQSweouRwZEXt8VIHu9.rb) (4.61 KB)

> **···**
>
> ------  
> 10652
> 
> Start off by looking for ways to satisfy:  
> &nbsp;&nbsp;7  
> + 5  
> ---  
> &nbsp;&nbsp;2
> 
> Then move further to the left:
> 
> &nbsp;&nbsp;67  
> + 85  
> ----  
> 152
> 
> The 52 is right, but not the 1. For addition and multiplication, we can just  
> take it mod 100, and if that works then its a possible partial solution.  
> For subtraction of two numbers, though, it doesn't work when it goes negative.  
> The trick in that case is to just mod the left-most digit:
> 
> &nbsp;&nbsp;9567  
> - 1085  
> ------  
> &nbsp;&nbsp;8482
> 
> &nbsp;&nbsp;7  
> - 5  
> ---  
> &nbsp;&nbsp;2 OK
> 
> &nbsp;&nbsp;67  
> - 85  
> ----  
> -22 =\> 82 (-2 mod 10 = 8)
> 
> verbal\_arithmetic.rb contains (old, ugly) code for generating permutations  
> and combinations. So the program works from right-to-left, finding partial  
> solutions that work by going through ways of mapping letters to numbers,  
> and for each one trying to move further left. Basically a depth-first search.
> 
> There are a number of other subteties, but like I said, I'm tired of messing  
> with this now, so I'll leave it there. Ask if you must.
> 
> Examples:
> 
> $ time ./verbal\_arithmetic.rb 'send more' + money  
> Found mapping:  
> &nbsp;&nbsp;m: 1  
> &nbsp;&nbsp;y: 2  
> &nbsp;&nbsp;n: 6  
> &nbsp;&nbsp;o: 0  
> &nbsp;&nbsp;d: 7  
> &nbsp;&nbsp;e: 5  
> &nbsp;&nbsp;r: 8  
> &nbsp;&nbsp;s: 9
> 
> real 0m1.074s  
> user 0m0.993s  
> sys 0m0.019s
> 
> $ ./verbal\_arithmetic.rb 'forty ten ten' + sixty  
> Found mapping:  
> &nbsp;&nbsp;x: 4  
> &nbsp;&nbsp;y: 6  
> &nbsp;&nbsp;n: 0  
> &nbsp;&nbsp;o: 9  
> &nbsp;&nbsp;e: 5  
> &nbsp;&nbsp;f: 2  
> &nbsp;&nbsp;r: 7  
> &nbsp;&nbsp;s: 3  
> &nbsp;&nbsp;t: 8  
> &nbsp;&nbsp;i: 1
> 
> $ ./verbal\_arithmetic.rb 'foo bar' - 'zag'  
> Found mapping:  
> &nbsp;&nbsp;a: 0  
> &nbsp;&nbsp;b: 3  
> &nbsp;&nbsp;z: 4  
> &nbsp;&nbsp;o: 1  
> &nbsp;&nbsp;f: 7  
> &nbsp;&nbsp;r: 9  
> &nbsp;&nbsp;g: 2
> 
> $ ./verbal\_arithmetic.rb 'fo ba' \* 'wag'  
> Found mapping:  
> &nbsp;&nbsp;w: 4  
> &nbsp;&nbsp;a: 2  
> &nbsp;&nbsp;b: 1  
> &nbsp;&nbsp;o: 5  
> &nbsp;&nbsp;f: 3  
> &nbsp;&nbsp;g: 0
> 
> --  
> Jesse Merriman  
> [jessemerriman@warpmail.net](mailto:jessemerriman@warpmail.net)  
> [http://www.jessemerriman.com/](http://www.jessemerriman.com/)

---

<div class="post-metadata">

**Author:** ![Eric\_I2](https://avatars.discourse-cdn.com/v4/letter/e/22d042/32.png) [@Eric\_I2](https://rubytalk.org/u/Eric_I2)\
**Post date:** [17 June 2007 19:30 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/7 "2007-06-17T19:30:24Z")

</div>

This program solves addition problems with any number of terms. It  
finds and displays all solutions to the problem.

The solving process is broken up into a sequence of simple steps all  
derived from class Step. A Step can be something such as 1) choosing  
an available digit for a given letter or 2) summing up a column and  
seeing if the result matches an already-assigned letter. As steps  
succeed the process continues with the following steps. But if a step  
fails (i.e., there's a contradiction) then the system backs up to a  
point where another choice can be made. This is handled by recursing  
through the sequence of steps. In fact, even when a solution is  
found, the program still backtracks to find other solutions.

The expectation is that by testing for contradictions as early as  
possible in the process we'll tend to avoid dead ends and the result  
will be much better than an exhaustive search.

For example, here are the steps for a sample equation:

&nbsp;&nbsp;&nbsp;send  
&nbsp;&nbsp;+more

> **···**
>
> -----  
> &nbsp;&nbsp;money
> 
> 1. Choose a digit for "d".  
> 2. Choose a digit for "e".  
> 3. Sum the column using letters "d", "e" (and include carry).  
> 4. Set the digit for "y" based on last column summed.  
> 5. Choose a digit for "n".  
> 6. Choose a digit for "r".  
> 7. Sum the column using letters "n", "r" (and include carry).  
> 8. Verify that last column summed matches current digit for "e".  
> 9. Choose a digit for "o".  
> 10. Sum the column using letters "e", "o" (and include carry).  
> 11. Verify that last column summed matches current digit for "n".  
> 12. Choose a digit for "s".  
> 13. Verify that "s" has not been assigned to zero.  
> 14. Choose a digit for "m".  
> 15. Verify that "m" has not been assigned to zero.  
> 16. Sum the column using letters "s", "m" (and include carry).  
> 17. Verify that last column summed matches current digit for "o".  
> 18. Sum the column using letters (and include carry).  
> 19. Verify that last column summed matches current digit for "m".  
> 20. Display a solution (provided carry is zero)!
> 
> Eric  
> ----  
> Are you interested in on-site Ruby training that's been highly  
> reviewed by former students? [http://LearnRuby.com](http://LearnRuby.com)
> 
> ====
> 
> # This is a solution to Ruby Quiz #128. As input it takes a "word  
> # equation" such as "send+more=money" and determines all possible  
> # mappings of letters to digits that yield a correct result.  
> #  
> # The constraints are: 1) a given digit can only be mapped to a single  
> # letter, 2) the first digit in any term cannot be zero.  
> #  
> # The solving process is broken up into a sequence of simple steps all  
> # derived from class Step. A Step can be something such as 1)  
> # choosing an available digit for a given letter or 2) summing up a  
> # column and seeing if the result matches an already-assigned letter.  
> # As steps succeed the process continues with the following steps.  
> # But if a step fails (i.e., there's a contradiction) then the system  
> # backs up to a point where another choice can be made. This is  
> # handled by recursing through the sequence of steps. In fact, even  
> # when a solution is found, the program still backtracks to find other  
> # solutions.
> 
> require 'set'
> 
> # State represents the stage of a partially solved word equation. It  
> # keeps track of what digits letters map to, which digits have not yet  
> # been assigned to letters, and the results of the last summed column,  
> # including the resulting digit and any carry if there is one.  
> class State  
> &nbsp;&nbsp;attr\_accessor :sum, :carry  
> &nbsp;&nbsp;attr\_reader :letters
> 
> &nbsp;&nbsp;def initialize()  
> &nbsp;&nbsp;&nbsp;&nbsp;@available\_digits = Set.new(0..9)  
> &nbsp;&nbsp;&nbsp;&nbsp;@letters = Hash.new  
> &nbsp;&nbsp;&nbsp;&nbsp;@sum, @carry = 0, 0  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;# Return digit for letter.  
> &nbsp;&nbsp;def [](letter)  
> &nbsp;&nbsp;&nbsp;&nbsp;@letters[letter]  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;# The the digit for a letter.  
> &nbsp;&nbsp;def []=(letter, digit)  
> &nbsp;&nbsp;&nbsp;&nbsp;# if the letter is currently assigned, return its digit to the  
> &nbsp;&nbsp;&nbsp;&nbsp;# available set  
> &nbsp;&nbsp;&nbsp;&nbsp;@available\_digits.add @letters[letter] if @letters[letter]
> 
> &nbsp;&nbsp;&nbsp;&nbsp;@letters[letter] = digit  
> &nbsp;&nbsp;&nbsp;&nbsp;@available\_digits.delete digit  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;# Clear the digit for a letter.  
> &nbsp;&nbsp;def clear(letter)  
> &nbsp;&nbsp;&nbsp;&nbsp;@available\_digits.add @letters[letter]  
> &nbsp;&nbsp;&nbsp;&nbsp;@letters[letter] = nil  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;# Return the available digits as an array copied from the set.  
> &nbsp;&nbsp;def available\_digits  
> &nbsp;&nbsp;&nbsp;&nbsp;@available\_digits.to\_a  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;# Tests whether a given digit is still available.  
> &nbsp;&nbsp;def available?(digit)  
> &nbsp;&nbsp;&nbsp;&nbsp;@available\_digits.member? digit  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;# Receives the total for a column and keeps track of it as the  
> &nbsp;&nbsp;# summed-to digit and any carry.  
> &nbsp;&nbsp;def column\_total=(total)  
> &nbsp;&nbsp;&nbsp;&nbsp;@sum = total % 10  
> &nbsp;&nbsp;&nbsp;&nbsp;@carry = total / 10  
> &nbsp;&nbsp;end  
> end
> 
> # Step is an "abstract" base level class from which all the "concrete"  
> # steps can be deriveds. It simply handles the storage of the next  
> # step in the sequence. Subclasses should provide 1) a to\_s method to  
> # describe the step being performed and 2) a perform method to  
> # actually perform the step.  
> class Step  
> &nbsp;&nbsp;attr\_writer :next\_step  
> end
> 
> # This step tries assigning each available digit to a given letter and  
> # continuing from there.  
> class ChooseStep \< Step  
> &nbsp;&nbsp;def initialize(letter)  
> &nbsp;&nbsp;&nbsp;&nbsp;@letter = letter  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;"Choose a digit for \"#{@letter}\"."  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def perform(state)  
> &nbsp;&nbsp;&nbsp;&nbsp;state.available\_digits.each do |v|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state[@letter] = v  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@next\_step.perform(state)  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;state.clear(@letter)  
> &nbsp;&nbsp;end  
> end
> 
> # This step sums up the given letters and changes to state to reflect  
> # the sum. Because we may have to backtrack, it stores the previous  
> # saved sum and carry for later restoration.  
> class SumColumnStep \< Step  
> &nbsp;&nbsp;def initialize(letters)  
> &nbsp;&nbsp;&nbsp;&nbsp;@letters = letters  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;list = @letters.map { |l| "\"#{l}\"" }.join(', ')  
> &nbsp;&nbsp;&nbsp;&nbsp;"Sum the column using letters #{list} (and include carry)."  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def perform(state)  
> &nbsp;&nbsp;&nbsp;&nbsp;# save sum and carry  
> &nbsp;&nbsp;&nbsp;&nbsp;saved\_sum, saved\_carry = state.sum, state.carry
> 
> &nbsp;&nbsp;&nbsp;&nbsp;state.column\_total =  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state.carry +  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@letters.inject(0) { |sum, letter| sum + state[letter] }  
> &nbsp;&nbsp;&nbsp;&nbsp;@next\_step.perform(state)
> 
> &nbsp;&nbsp;&nbsp;&nbsp;# restore sum and carry  
> &nbsp;&nbsp;&nbsp;&nbsp;state.sum, state.carry = saved\_sum, saved\_carry  
> &nbsp;&nbsp;end  
> end
> 
> # This step determines the digit for a letter given the last column  
> # summed. If the digit is not available, then we cannot continue.  
> class AssignOnSumStep \< Step  
> &nbsp;&nbsp;def initialize(letter)  
> &nbsp;&nbsp;&nbsp;&nbsp;@letter = letter  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;"Set the digit for \"#{@letter}\" based on last column summed."  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def perform(state)  
> &nbsp;&nbsp;&nbsp;&nbsp;if state.available? state.sum  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state[@letter] = state.sum  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@next\_step.perform(state)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state.clear(@letter)  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end  
> end
> 
> # This step will occur after a column is summed, and the result must  
> # match a letter that's already been assigned.  
> class CheckOnSumStep \< Step  
> &nbsp;&nbsp;def initialize(letter)  
> &nbsp;&nbsp;&nbsp;&nbsp;@letter = letter  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;"Verify that last column summed matches current " +  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"digit for \"#{@letter}\"."  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def perform(state)  
> &nbsp;&nbsp;&nbsp;&nbsp;@next\_step.perform(state) if state[@letter] == state.sum  
> &nbsp;&nbsp;end  
> end
> 
> # This step will occur after a letter is assigned to a digit if the  
> # letter is not allowed to be a zero, because one or more terms begins  
> # with that letter.  
> class CheckNotZeroStep \< Step  
> &nbsp;&nbsp;def initialize(letter)  
> &nbsp;&nbsp;&nbsp;&nbsp;@letter = letter  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;"Verify that \"#{@letter}\" has not been assigned to zero."  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def perform(state)  
> &nbsp;&nbsp;&nbsp;&nbsp;@next\_step.perform(state) unless state[@letter] == 0  
> &nbsp;&nbsp;end  
> end
> 
> # This step represents finishing the equation. The carry must be zero  
> # for the perform to have found an actual result, so check that and  
> # display a digit -\> letter conversion table and dispaly the equation  
> # with the digits substituted in for the letters.  
> class FinishStep \< Step  
> &nbsp;&nbsp;def initialize(equation)  
> &nbsp;&nbsp;&nbsp;&nbsp;@equation = equation  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;"Display a solution (provided carry is zero)!"  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def perform(state)  
> &nbsp;&nbsp;&nbsp;&nbsp;# we're supposedly done, so there can't be anything left in carry  
> &nbsp;&nbsp;&nbsp;&nbsp;return unless state.carry == 0
> 
> &nbsp;&nbsp;&nbsp;&nbsp;# display a letter to digit table on a single line  
> &nbsp;&nbsp;&nbsp;&nbsp;table = state.letters.invert  
> &nbsp;&nbsp;&nbsp;&nbsp;puts  
> &nbsp;&nbsp;&nbsp;&nbsp;puts table.keys.sort.map { |k| "#{table[k]}=#{k}" }.join(' ')
> 
> &nbsp;&nbsp;&nbsp;&nbsp;# display the equation with digits substituted for the letters  
> &nbsp;&nbsp;&nbsp;&nbsp;equation = @equation.dup  
> &nbsp;&nbsp;&nbsp;&nbsp;state.letters.each { |k, v| equation.gsub!(k, v.to\_s) }  
> &nbsp;&nbsp;&nbsp;&nbsp;puts  
> &nbsp;&nbsp;&nbsp;&nbsp;puts equation  
> &nbsp;&nbsp;end  
> end
> 
> # Do a basic test for the command-line arguments validity.  
> unless ARGV[0] =~ Regexp.new('^[a-z]+(\+[a-z]+)\*=[a-z]+$')  
> &nbsp;&nbsp;STDERR.puts "invalid argument"  
> &nbsp;&nbsp;exit 1  
> end
> 
> # Split the command-line argument into terms and figure out how many  
> # columns we're dealing with.  
> terms = ARGV[0].split(/\+|=/)  
> column\_count = terms.map { |e| e.size }.max
> 
> # Build the display of the equation a line at a time. The line  
> # containing the final term of the sum has to have room for the plus  
> # sign.  
> display\_columns = [column\_count, terms[-2].size + 1].max  
> display = []  
> terms[0..-3].each do |term|  
> &nbsp;&nbsp;display \<\< term.rjust(display\_columns)  
> end  
> display \<\< "+" + terms[-2].rjust(display\_columns - 1)  
> display \<\< "-" \* display\_columns  
> display \<\< terms[-1].rjust(display\_columns)  
> display = display.join("\n")  
> puts display
> 
> # AssignOnSumStep which letters cannot be zero since they're the first  
> # letter of a term.  
> nonzero\_letters = Set.new  
> terms.each { |e| nonzero\_letters.add(e[0, 1]) }
> 
> # A place to keep track of which letters have so-far been assigned.  
> chosen\_letters = Set.new
> 
> # Build up the steps needed to solve the equation.  
> steps = []  
> column\_count.times do |column|  
> &nbsp;&nbsp;index = -column - 1  
> &nbsp;&nbsp;letters = [] # letters for this column to be added
> 
> &nbsp;&nbsp;terms[0..-2].each do |term| # for each term that's being added...  
> &nbsp;&nbsp;&nbsp;&nbsp;letter = term[index, 1]  
> &nbsp;&nbsp;&nbsp;&nbsp;next if letter.nil? # skip term if no letter in column  
> &nbsp;&nbsp;&nbsp;&nbsp;letters \<\< letter # note that this letter is part of sum
> 
> &nbsp;&nbsp;&nbsp;&nbsp;# if the letter does not have a digit, create a ChooseStep  
> &nbsp;&nbsp;&nbsp;&nbsp;unless chosen\_letters.member? letter  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;steps \<\< ChooseStep.new(letter)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;chosen\_letters.add(letter)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;steps \<\< CheckNotZeroStep.new(letter) if  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nonzero\_letters.member? letter  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;# create a SumColumnStep for the column  
> &nbsp;&nbsp;steps \<\< SumColumnStep.new(letters)
> 
> &nbsp;&nbsp;summed\_letter = terms[-1][index, 1] # the letter being summed to
> 
> &nbsp;&nbsp;# check whether the summed to letter should already have a digit  
> &nbsp;&nbsp;if chosen\_letters.member? summed\_letter  
> &nbsp;&nbsp;&nbsp;&nbsp;# should already have a digit, check that summed digit matches it  
> &nbsp;&nbsp;&nbsp;&nbsp;steps \<\< CheckOnSumStep.new(summed\_letter)  
> &nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;# doesn't already have digit, so create a AssignOnSumStep for  
> &nbsp;&nbsp;&nbsp;&nbsp;# letter  
> &nbsp;&nbsp;&nbsp;&nbsp;steps \<\< AssignOnSumStep.new(summed\_letter)  
> &nbsp;&nbsp;&nbsp;&nbsp;chosen\_letters.add(summed\_letter)
> 
> &nbsp;&nbsp;&nbsp;&nbsp;# check whether this letter cannot be zero and if so add a  
> &nbsp;&nbsp;&nbsp;&nbsp;# CheckNotZeroStep  
> &nbsp;&nbsp;&nbsp;&nbsp;steps \<\< CheckNotZeroStep.new(summed\_letter) if  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nonzero\_letters.member? summed\_letter  
> &nbsp;&nbsp;end  
> end
> 
> # should be done, so add a FinishStep  
> steps \<\< FinishStep.new(display)
> 
> # print out all the steps  
> # steps.each\_with\_index { |step, i| puts "#{i + 1}. #{step}" }
> 
> # let each step know about the one that follows it.  
> steps.each\_with\_index { |step, i| step.next\_step = steps[i + 1] }
> 
> # start performing with the first step.  
> steps.first.perform(State.new)

---

<div class="post-metadata">

**Author:** ![Justin\_Ethier](https://avatars.discourse-cdn.com/v4/letter/j/c67d28/32.png) [@Justin\_Ethier](https://rubytalk.org/u/Justin_Ethier)\
**Post date:** [18 June 2007 03:28 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/8 "2007-06-18T03:28:10Z")

</div>

Hello everyone,

At the outset, I had no idea how difficult this problem would be. In fact,  
it turns out that this is actually an NP-complete problem if the number base  
is not fixed at 10. Anyway... Initially I experimented with a backtracking  
solution, but ran into trouble getting it to work properly. So for now I am  
submitting this solution. Although less than perfect, it does work for the  
test inputs, as well as additional solutions I have tried. The idea is to  
brute force a solution by trying all possible combinations of solutions  
until one works.

For this solution, I used the Permutation Gem available here:  
[http://permutation.rubyforge.org/doc/index.html](http://permutation.rubyforge.org/doc/index.html)

require 'Permutation'

To avoid having to (re)write the combination code myself, I also used the  
Combinations class from:  
[http://butunclebob.com/ArticleS.UncleBob.RubyCombinations](http://butunclebob.com/ArticleS.UncleBob.RubyCombinations)  
This is great code, someone really should create a gem for it!

(see link for the code)

I packaged all of my code inside the following class:

class VerbalArithmetic

&nbsp;&nbsp;# Parse given equation into lvalues (words on the left-hand side of the  
'=' that  
&nbsp;&nbsp;# are to be added together) and an rvalue (the single word on the  
right-hand side)  
&nbsp;&nbsp;def parse\_equation (equation)  
&nbsp;&nbsp;&nbsp;&nbsp;lvalues = equation.split("+")  
&nbsp;&nbsp;&nbsp;&nbsp;rvalue = lvalues[-1].split("=")  
&nbsp;&nbsp;&nbsp;&nbsp;lvalues[-1] = rvalue[0] # Get last lvalue  
&nbsp;&nbsp;&nbsp;&nbsp;rvalue = rvalue[1] # Get rvalue

&nbsp;&nbsp;&nbsp;&nbsp;return lvalues, rvalue  
&nbsp;&nbsp;end

&nbsp;&nbsp;# Brute force a solution by trying all possible combinations  
&nbsp;&nbsp;def find\_solution(lvalues, rvalue)

&nbsp;&nbsp;&nbsp;&nbsp;# Form a list of all letters  
&nbsp;&nbsp;&nbsp;&nbsp;words = Marshal::load(Marshal::dump(lvalues))  
&nbsp;&nbsp;&nbsp;&nbsp;words.push(rvalue)  
&nbsp;&nbsp;&nbsp;&nbsp;letters = {}  
&nbsp;&nbsp;&nbsp;&nbsp;words.each do |word|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;word.split("").each do |letter|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;letters[letter] = letter if letters[letter] == nil  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;# Format l/r values to ease solution analysis below  
&nbsp;&nbsp;&nbsp;&nbsp;lvalues\_formatted =   
&nbsp;&nbsp;&nbsp;&nbsp;lvalues.each {|lval| lvalues\_formatted.push(lval.reverse.split(""))}  
&nbsp;&nbsp;&nbsp;&nbsp;rvalue\_formatted = rvalue.reverse.split("")

&nbsp;&nbsp;&nbsp;&nbsp;# For all unordered combinations of numbers...  
&nbsp;&nbsp;&nbsp;&nbsp;for i in Combinations.get(10, letters.values.size)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# For all permutations of each combination...  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;perm = Permutation.for(i)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;perm.each do |p|

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Map each combination of numbers to the underlying letters  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map = {}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;parry = p.project  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for i in 0...letters.size  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map[letters.values[i]] = parry[i]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Does this mapping yield a solution?  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if is\_solution?(lvalues\_formatted, rvalue\_formatted, map)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return map  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;nil  
&nbsp;&nbsp;end

&nbsp;&nbsp;# Determines if the given equation may be solved by  
&nbsp;&nbsp;# substituting the given number for its letters  
&nbsp;&nbsp;def is\_solution?(lvalues, rvalue, map)

&nbsp;&nbsp;&nbsp;&nbsp;# Make sure there are no leading zero's  
&nbsp;&nbsp;&nbsp;&nbsp;for lval in lvalues  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return false if map[lval[-1]] == 0  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;return false if map[rvalue[-1]] == 0

&nbsp;&nbsp;&nbsp;&nbsp;# Perform arithmetic using the mappings, and make sure they are valid  
&nbsp;&nbsp;&nbsp;&nbsp;remainder = 0  
&nbsp;&nbsp;&nbsp;&nbsp;for i in 0...rvalue.size  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lvalues.each do |lval|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;remainder = remainder + map[lval[i]] if map[lval[i]] != nil # Sum  
values  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return false if (remainder % 10) != map[rvalue[i]] # Validate digit  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;remainder = remainder / 10 # Truncate value at  
this place  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;true  
&nbsp;&nbsp;end  
end

Finally, this code puts everything together:

va = VerbalArithmetic.new  
lvalues, rvalue = va.parse\_equation("send+more=money")  
map = va.find\_solution(lvalues, rvalue)

puts "Solution: ", map if map != nil

And here is the output:

Solution:  
m1n6y2d7o0e5r8s9

Thanks,

Justin

> **···**
>
> On 6/15/07, Ruby Quiz \<james@grayproductions.net\> wrote:
> 
> > The three rules of Ruby Quiz:
> > 
> > 1. Please do not post any solutions or spoiler discussion for this quiz  
> > until  
> > 48 hours have passed from the time on this message.
> > 
> > 2. Support Ruby Quiz by submitting ideas as often as you can:
> > 
> > [http://www.rubyquiz.com/](http://www.rubyquiz.com/)
> > 
> > 3. Enjoy!
> > 
> > Suggestion: A [QUIZ] in the subject of emails about the problem helps  
> > everyone  
> > on Ruby Talk follow the discussion. Please reply to the original quiz  
> > message,  
> > if you can.
> > 
> > -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
> > 
> > A famous set of computer problems involve verbal arithmetic. In these  
> > problems,  
> > you are given equations of words like:
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ more  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;------  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;money
> > 
> > or:
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;forty  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ten  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ ten  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-------  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sixty
> > 
> > The goal is to find a single digit for each letter that makes the equation  
> > true.  
> > Normal rules of number construction apply, so the first digit of a  
> > multi-digit  
> > number should be nonzero and each letter represents a different digit.
> > 
> > This week's quiz is to build a program that reads in equations and outputs  
> > solutions. You can decide how complex of an equation you want to support,  
> > with  
> > the examples above being the minimum implementation.
> > 
> > Here's a solution you can test against:
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$ ruby verbal\_arithmetic.rb 'send+more=money'  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s: 9  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e: 5  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;n: 6  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d: 7  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m: 1  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;o: 0  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r: 8  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y: 2

---

<div class="post-metadata">

**Author:** ![Glen\_F\_Pankow](https://avatars.discourse-cdn.com/v4/letter/g/0ea827/32.png) [@Glen\_F\_Pankow](https://rubytalk.org/u/Glen_F_Pankow)\
**Post date:** [18 June 2007 11:33 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/9 "2007-06-18T11:33:03Z")

</div>

#! /usr/bin/env ruby

> **···**
>
> #  
> # quiz-128 -- Ruby Quiz #128 -- Verbal Arithmetic.  
> #  
> # Usage: quiz-128 [-v] '\<equation string\>'  
> # or: quiz-128 [-v] \<addend\> \<addend\> [\<addend\> ...] \<sum\>  
> #  
> # See the Ruby Quiz #128 documentation for more information  
> # ([http://www.rubyquiz.com/quiz128.html](http://www.rubyquiz.com/quiz128.html)).  
> #  
> # Glen Pankow 06/17/07 Original version.  
> #  
> # Licensed under the Ruby License.  
> #  
> #-----------------------------------------------------------------------------  
> #  
> # I take a slightly different approach to this quiz: I build up code that  
> # kinda-sorta models addition as taught in U.S. elementary schools (taking  
> # into account the various constraints of the problem), then eval it.  
> #  
> # And instead of generating permutations of unassigned digits and using  
> # recursion for backtracking, I have a single array of digits that is used  
> # sort of as a mask (nil entries mark assigned digits) and use Ruby's  
> # wonderful magic iterator facility to scan through it.  
> #  
> # $ uname -srmpio  
> # Linux 2.6.9-55.ELsmp i686 i686 i386 GNU/Linux  
> #  
> # $ time ./quiz-128 'send + more = money'  
> # d = 7, e = 5, y = 2, n = 6, r = 8, o = 0, s = 9, m = 1  
> # 1 0 1 1  
> # s:9 e:5 n:6 d:7  
> # + m:1 o:0 r:8 e:5  
> # ---------------------  
> # m:1 o:0 n:6 e:5 y:2  
> # 0.065u 0.003s 0:00.07 85.7% 0+0k 0+0io 0pf+0w  
> #  
> # $ time ./quiz-128 forty ten ten sixty  
> # y = 6, n = 0, t = 8, e = 5, r = 7, x = 4, o = 9, i = 1, f = 2, s = 3  
> # 1 2 1 0  
> # f:2 o:9 r:7 t:8 y:6  
> # t:8 e:5 n:0  
> # + t:8 e:5 n:0  
> # ---------------------  
> # s:3 i:1 x:4 t:8 y:6  
> # 0.029u 0.002s 0:00.03 66.6% 0+0k 0+0io 0pf+0w  
> #  
> # $ time ./quiz-128 eat+that=apple  
> # t = 9, e = 8, a = 1, l = 3, h = 2, p = 0  
> # 1 1 0 1  
> # e:8 a:1 t:9  
> # + t:9 h:2 a:1 t:9  
> # ---------------------  
> # a:1 p:0 p:0 l:3 e:8  
> # 0.008u 0.002s 0:00.01 0.0% 0+0k 0+0io 0pf+0w  
> #  
> # $ time ./quiz-128 ruby rubber baby buggy bumper  
> # y = 0, r = 7, b = 8, e = 1, g = 4, u = 2, a = 3, p = 9, m = 6  
> # ...  
> # y = 0, r = 7, b = 8, e = 5, g = 4, u = 2, a = 3, p = 9, m = 6  
> # 1 2 1 2 0  
> # r:7 u:2 b:8 y:0  
> # r:7 u:2 b:8 b:8 e:5 r:7  
> # b:8 a:3 b:8 y:0  
> # + b:8 u:2 g:4 g:4 y:0  
> # -------------------------  
> # b:8 u:2 m:6 p:9 e:5 r:7  
> # 0.120u 0.002s 0:00.13 92.3% 0+0k 0+0io 0pf+0w  
> #
> 
> class Array
> 
> &nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;# For each non-nil element of the current array, (destructively) set it to  
> &nbsp;&nbsp;&nbsp;&nbsp;# nil (i.e., 'marking' it), yield the original value (to the assumed block),  
> &nbsp;&nbsp;&nbsp;&nbsp;# and restore it back to what it originally was (i.e., 'unmarking' it).  
> &nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;# For example, the code:  
> &nbsp;&nbsp;&nbsp;&nbsp;# array = [0, nil, 2, 3]  
> &nbsp;&nbsp;&nbsp;&nbsp;# p "before: array = #{array.inspect}"  
> &nbsp;&nbsp;&nbsp;&nbsp;# array.unmarkeds { |elem| p "elem = #{elem}, array = #{array.inspect}" }  
> &nbsp;&nbsp;&nbsp;&nbsp;# p "after: array = #{array.inspect}"  
> &nbsp;&nbsp;&nbsp;&nbsp;# would print:  
> &nbsp;&nbsp;&nbsp;&nbsp;# before: array = [0, nil, 2, 3]  
> &nbsp;&nbsp;&nbsp;&nbsp;# elem = 0, array = [nil, nil, 2, 3]  
> &nbsp;&nbsp;&nbsp;&nbsp;# elem = 2, array = [0, nil, nil, 3]  
> &nbsp;&nbsp;&nbsp;&nbsp;# elem = 3, array = [0, nil, 2, nil]  
> &nbsp;&nbsp;&nbsp;&nbsp;# after: array = [0, nil, 2, 3]  
> &nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;def unmarkeds  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(0...size).each do |i|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next if (at(i).nil?)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elem = at(i) ; self[i] = nil  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yield elem  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self[i] = elem  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;# If the \<i\>-th element of the current array is nil, do nothing. Otherwise  
> &nbsp;&nbsp;&nbsp;&nbsp;# (destructively) set it to nil (i.e., 'marking' it), yield (to the assumed  
> &nbsp;&nbsp;&nbsp;&nbsp;# block), and restore it back to what it originally was (i.e., 'unmarking'  
> &nbsp;&nbsp;&nbsp;&nbsp;# it). This is basically the guts of unmarkeds(), and is provided for safe  
> &nbsp;&nbsp;&nbsp;&nbsp;# manual element marking.  
> &nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;def if\_unmarked(i)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return if (at(i).nil?)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elem = at(i) ; self[i] = nil  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yield  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self[i] = elem  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;# Note: typically one might say 'yield elem, self' in these methods, but  
> &nbsp;&nbsp;&nbsp;&nbsp;# I don't need them for this application due to Ruby's scoping mechanism.  
> &nbsp;&nbsp;&nbsp;&nbsp;#  
> end
> 
> #  
> # Process the command-line arguments of addend strings and the sum string.  
> #  
> verbose = false  
> addend\_strs = []  
> ARGV.each do |arg|  
> &nbsp;&nbsp;&nbsp;&nbsp;if (arg == '-v')  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;verbose = true  
> &nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;arg.split(/[\s+=]+/).each { |term| addend\_strs \<\< term }  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> end  
> addend\_strs = ['send', 'more', 'money'] if (addend\_strs.empty?)  
> sum\_str = addend\_strs.pop
> 
> #  
> # Split the strings up into their component letters; create some (ugly) code to  
> # eventually print out a nice table of the addition.  
> #  
> table\_print\_code = 'print " '  
> (sum\_str.length - 1).downto(1) do |i|  
> &nbsp;&nbsp;&nbsp;&nbsp;table\_print\_code \<\< " \#{carry#{i}}"  
> end  
> table\_print\_code \<\< "\\n\"\n"  
> addends = []  
> first\_letters = { }  
> (0...addend\_strs.size).each do |i|  
> &nbsp;&nbsp;&nbsp;&nbsp;addend\_str = addend\_strs[i]  
> &nbsp;&nbsp;&nbsp;&nbsp;addend\_chars = addend\_str.split(//).reverse  
> &nbsp;&nbsp;&nbsp;&nbsp;addends \<\< addend\_chars  
> &nbsp;&nbsp;&nbsp;&nbsp;first\_letters[addend\_chars[-1]] = 1  
> &nbsp;&nbsp;&nbsp;&nbsp;table\_print\_code \  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\<\< 'print "' \<\< ((i \< addend\_strs.size - 1)? ' ' : '+') \  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\<\< ' ' \* (sum\_str.length - addend\_str.length) \  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\<\< addend\_str.gsub(/([a-z])/, ' \1:#{\1}') \<\< "\\n\"\n"  
> end  
> table\_print\_code \  
> &nbsp;&nbsp;\<\< 'print "-' \<\< ('----' \* sum\_str.length) \<\< "\\n\"\n" \  
> &nbsp;&nbsp;\<\< 'print " ' \<\< sum\_str.gsub(/([a-z])/, ' \1:#{\1}') \<\< "\\n\"\n"  
> sum\_chars = sum\_str.split(//).reverse  
> first\_letters[sum\_chars[-1]] = 1
> 
> #  
> # Build the addition code.  
> #  
> # This, too, is quite ugly and I don't bother to document it, as printing out  
> # the generated code will probably give one a better idea of how it works than  
> # my usual verbose documentation (i.e., run this script with -v).  
> #  
> seen\_chars = { }  
> code\_head = "rem\_digs = (0..9).to\_a\n"  
> code\_tail = ''  
> answer\_print\_code = 'print "'  
> indent = ''  
> (0...sum\_chars.size).each do |col|  
> &nbsp;&nbsp;&nbsp;&nbsp;sum\_char = sum\_chars[col]  
> &nbsp;&nbsp;&nbsp;&nbsp;col\_sum\_code = "#{indent}carry#{col+1}, #{sum\_char} = (carry#{col}"  
> &nbsp;&nbsp;&nbsp;&nbsp;addends.inject([]) { |dc, addend| dc \<\< addend[col] }.each do |dig\_char|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next if (dig\_char.nil?)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (seen\_chars[dig\_char].nil?)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;code\_head \<\< "#{indent}rem\_digs.unmarkeds do |#{dig\_char}|\n"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;code\_tail[0,0] = "#{indent}end\n"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;indent \<\< ' '  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;seen\_chars[dig\_char] = 1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;code\_head \<\< "#{indent}next if (#{dig\_char} == 0) # leading 0?\n" \  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (first\_letters.has\_key?(dig\_char))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;col\_sum\_code[0,0] = ' ' # fix indentation  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;answer\_print\_code \<\< ", #{dig\_char} = \#{#{dig\_char}}"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;col\_sum\_code \<\< " + #{dig\_char}"  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;col\_sum\_code \<\< ").divmod(10)\n"  
> &nbsp;&nbsp;&nbsp;&nbsp;col\_sum\_code.sub!(/\(carry0 \+ /, '(')  
> &nbsp;&nbsp;&nbsp;&nbsp;if (seen\_chars[sum\_char].nil?)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;code\_head \<\< col\_sum\_code  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;code\_head \<\< "#{indent}next if (#{sum\_char} == 0) # leading 0?\n" \  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (first\_letters.has\_key?(sum\_char))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;code\_head \<\< "#{indent}rem\_digs.if\_unmarked(#{sum\_char}) do\n"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;code\_tail[0,0] = "#{indent}end\n"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;indent \<\< ' '  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;seen\_chars[sum\_char] = 1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;answer\_print\_code \<\< ", #{sum\_char} = \#{#{sum\_char}}"  
> &nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;col\_sum\_code.sub!(/ = /, '2 = ')  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;code\_head \  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\<\< col\_sum\_code \  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\<\< "#{indent}next unless (#{sum\_char}2 == #{sum\_char}) # inconsistent?\n"  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> end  
> answer\_print\_code.sub!(/\", /, '"\n')  
> answer\_print\_code \<\< "\\n\"\n"
> 
> #  
> # And print out the code (if verbose) and run it!  
> #  
> code = code\_head + answer\_print\_code + table\_print\_code + code\_tail  
> print code, "\n" if (verbose)  
> eval(code)

---

<div class="post-metadata">

**Author:** ![Morton\_Goldberg](https://avatars.discourse-cdn.com/v4/letter/m/7ea924/32.png) [@Morton\_Goldberg](https://rubytalk.org/u/Morton_Goldberg)\
**Post date:** [18 June 2007 14:55 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/10 "2007-06-18T14:55:32Z")

</div>

Here is my solution for Ruby Quiz 128. It's a little rough, but I can't afford to spend any more time working on it. One thing I didn't have time to do was to provide a user interface. Another thing I didn't do was proper commenting. I apologize for the latter.

I thought a Darwinian search (aka genetic algorithm) would be an interesting way to tackle this quiz. I have been looking for a excuse to write such a search in Ruby for quite awhile and this seemed to be it.

Here are is the output from one run of my quiz solution:

\<result\>  
Solution found after 15 steps  
SEND+MORE=MONEY  
9567+1085=10652

Solution found after 27 steps  
FORTY+TEN+TEN=SIXTY  
29786+850+850=31486  
\</result\>

And here is the code:

\<code\>  
#! /usr/bin/env ruby -w

> **···**
>
> #  
> # solution.rb  
> # Quiz 128  
> #  
> # Created by Morton Goldberg on 2007-06-18.
> 
> # Assumption: equations take the form: term + term + ... term = sum
> 
> ROOT\_DIR = File.dirname(\_\_FILE\_\_)  
> $LOAD\_PATH \<\< File.join(ROOT\_DIR, "lib")
> 
> require "cryptarithm"  
> require "solver"
> 
> EQUATION\_1 = "SEND+MORE=MONEY"  
> EQUATION\_2 = "FORTY+TEN+TEN=SIXTY"  
> POP\_SIZE = 400  
> FECUNDITY = 2  
> STEPS = 50
> 
> Cryptarithm.equation(EQUATION\_1)  
> s = Solver.new(POP\_SIZE, FECUNDITY, STEPS)  
> s.run  
> puts s.show
> 
> Cryptarithm.equation(EQUATION\_2)  
> s = Solver.new(POP\_SIZE, FECUNDITY, STEPS)  
> s.run  
> puts s.show  
> \</code\>
> 
> And here are the library classes:
> 
> \<code\>  
> # lib/cryptarithm.rb  
> # Quiz 128  
> #  
> # Created by Morton Goldberg on 2007-06-18.
> 
> DIGITS = (0..9).to\_a
> 
> class Cryptarithm  
> &nbsp;&nbsp;&nbsp;&nbsp;@@equation = ""  
> &nbsp;&nbsp;&nbsp;&nbsp;@@max\_rank = -1  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.equation(str=nil)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if str  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@@equation = str.upcase  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lhs, rhs = @@equation.gsub(/[A-Z]/, "9").split("=")  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@@max\_rank = [eval(lhs), eval(rhs)].max  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@@equation  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;attr\_accessor :ranking, :solution  
> &nbsp;&nbsp;&nbsp;&nbsp;def initialize  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@solution = @@equation.delete("+-=").split("").uniq  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@solution = @solution.zip((DIGITS.sort\_by {rand})[0, @solution.size])  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rank  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def mutate(where=rand(@solution.size))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise RangeError unless (0...@solution.size).include?(where)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;digits = @solution.collect { |pair| pair[1] }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;digits = DIGITS - digits  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return if digits.empty?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@solution[where][1] = digits[rand(digits.size)]  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def swap  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m = rand(@solution.size)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;n = m  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while n == m  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;n = rand(@solution.size)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@solution[m][1], @solution[n][1] = @solution[n][1], @solution[m][1]  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def rank  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sum = @@equation.dup  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;solution.each { |chr, num| sum.gsub!(chr, num.to\_s) }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lhs, rhs = sum.split("=")  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;terms = lhs.split("+") \<\< rhs  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if terms.any? { |t| t[0] == ?0 }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@ranking = @@max\_rank  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@ranking = eval("#{lhs} - #{rhs}").abs  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def initialize\_copy(original)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@solution = original.solution.collect { |pair| pair.dup }  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def inspect  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[@ranking, @solution].inspect  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sum = @@equation.dup  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;solution.each { |chr, num| sum.gsub!(chr, num.to\_s) }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"#{@@equation}\n#{sum}"  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> end  
> \</code\>
> 
> \<code\>  
> # lib/solver.rb  
> # Quiz 128  
> #  
> # Created by Morton Goldberg on 2007-06-18.  
> #  
> # Attempts to a solve cryptarithm puzzle by applying a Darwinian search  
> # (aka genetic algorithm). It can thought of as a stochastic breadth-first  
> # search. Although this method doesn't guarantee a solution will be  
> # found, it often finds one quite quickly.
> 
> MUTATION = 0.5  
> SWAP = 1.0
> 
> class Solver  
> &nbsp;&nbsp;&nbsp;&nbsp;attr\_reader :best, :population, :step  
> &nbsp;&nbsp;&nbsp;&nbsp;def initialize(pop\_size, fecundity, steps)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@pop\_size = pop\_size  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fecundity = fecundity  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@steps = steps  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mid\_step = steps / 2  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@step = 1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@population = []  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@pop\_size.times { @population \<\< Cryptarithm.new }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;select  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def run  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@steps.times do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;replicate  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;select  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break if @best.rank.zero?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@step += 1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@best  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def replicate  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@pop\_size.times do |n|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;crypt = @population[n]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# mate = crypt  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# while mate.equal?(crypt)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# mate = @population[rand(@pop\_size)]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fecundity.times do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;child = crypt.dup  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;child.mutate if crypt.solution.size \< 10 && rand \<= MUTATION  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;child.swap if rand \<= SWAP  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@population \<\< child  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def select  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@population = @population.sort\_by { |crypt| crypt.rank }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@population = @population[0, @pop\_size]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@best = @population.first  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def show  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if @step \> @steps  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"No solution found after #{step} steps"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Solution found after #{step} steps\n" + @best.to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> end  
> \</code\>
> 
> Regards, Morton

---

<div class="post-metadata">

**Author:** ![Eric\_I2](https://avatars.discourse-cdn.com/v4/letter/e/22d042/32.png) [@Eric\_I2](https://rubytalk.org/u/Eric_I2)\
**Post date:** [18 June 2007 16:45 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/11 "2007-06-18T16:45:02Z")

</div>

If anyone would like more samples to try their code with, I found the  
following on the web:

\* [http://www.gtoal.com/wordgames/alphametic/testscript](http://www.gtoal.com/wordgames/alphametic/testscript)

\* [http://www.gtoal.com/wordgames/alphametic/testscript.out](http://www.gtoal.com/wordgames/alphametic/testscript.out)

The first is a script which poses the problems to their own solving-  
program. The second shows the solutions that their program produces.

Some of the sample equations have multiple solutions, such as 'eins  
+eins=zwei'. 'united+states=america' has no solutions. And of the  
few that I've tested with my own solution, 'saturn+pluto+uranus  
+neptune=planets' takes the longest to solve.

Eric

> **···**
>
> ----  
> Interested in hands-on, on-site Ruby training? See [http://LearnRuby.com](http://LearnRuby.com)  
> for information about a well-reviewed class.

---

<div class="post-metadata">

**Author:** ![Morton\_Goldberg](https://avatars.discourse-cdn.com/v4/letter/m/7ea924/32.png) [@Morton\_Goldberg](https://rubytalk.org/u/Morton_Goldberg)\
**Post date:** [19 June 2007 04:39 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/12 "2007-06-19T04:39:30Z")

</div>

Sorry for the second post, but I just noticed that I pasted a wrong (earlier) version of solver.rb into my first post. This is what I intended to post.

The difference between the two versions is minor, but this version eliminates some commented-out experimental code and corrects a minor bug.

Sample output:

\<result\>  
Solution found after 15 steps  
SEND+MORE=MONEY  
9567+1085=10652

Solution found after 27 steps  
FORTY+TEN+TEN=SIXTY  
29786+850+850=31486  
\</result\>

And here is the corrected code:

\<code\>  
#! /usr/bin/env ruby -w

> **···**
>
> #  
> # solution.rb  
> # Quiz 128  
> #  
> # Created by Morton Goldberg on 2007-06-18.
> 
> # Assumption: equations take the form: term + term + ... term = sum
> 
> ROOT\_DIR = File.dirname(\_\_FILE\_\_)  
> $LOAD\_PATH \<\< File.join(ROOT\_DIR, "lib")
> 
> require "cryptarithm"  
> require "solver"
> 
> EQUATION\_1 = "SEND+MORE=MONEY"  
> EQUATION\_2 = "FORTY+TEN+TEN=SIXTY"  
> POP\_SIZE = 400  
> FECUNDITY = 2  
> STEPS = 50
> 
> Cryptarithm.equation(EQUATION\_1)  
> s = Solver.new(POP\_SIZE, FECUNDITY, STEPS)  
> s.run  
> puts s.show
> 
> Cryptarithm.equation(EQUATION\_2)  
> s = Solver.new(POP\_SIZE, FECUNDITY, STEPS)  
> s.run  
> puts s.show  
> \</code\>
> 
> And here are the library classes:
> 
> \<code\>  
> # lib/cryptarithm.rb  
> # Quiz 128  
> #  
> # Created by Morton Goldberg on 2007-06-18.
> 
> DIGITS = (0..9).to\_a
> 
> class Cryptarithm  
> &nbsp;&nbsp;&nbsp;&nbsp;@@equation = ""  
> &nbsp;&nbsp;&nbsp;&nbsp;@@max\_rank = -1  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.equation(str=nil)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if str  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@@equation = str.upcase  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lhs, rhs = @@equation.gsub(/[A-Z]/, "9").split("=")  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@@max\_rank = [eval(lhs), eval(rhs)].max  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@@equation  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;attr\_accessor :ranking, :solution  
> &nbsp;&nbsp;&nbsp;&nbsp;def initialize  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@solution = @@equation.delete("+-=").split("").uniq  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@solution = @solution.zip((DIGITS.sort\_by {rand})[0, @solution.size])  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rank  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def mutate(where=rand(@solution.size))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise RangeError unless (0...@solution.size).include?(where)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;digits = @solution.collect { |pair| pair[1] }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;digits = DIGITS - digits  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return if digits.empty?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@solution[where][1] = digits[rand(digits.size)]  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def swap  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m = rand(@solution.size)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;n = m  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while n == m  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;n = rand(@solution.size)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@solution[m][1], @solution[n][1] = @solution[n][1], @solution[m][1]  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def rank  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sum = @@equation.dup  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;solution.each { |chr, num| sum.gsub!(chr, num.to\_s) }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lhs, rhs = sum.split("=")  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;terms = lhs.split("+") \<\< rhs  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if terms.any? { |t| t[0] == ?0 }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@ranking = @@max\_rank  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@ranking = eval("#{lhs} - #{rhs}").abs  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def initialize\_copy(original)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@solution = original.solution.collect { |pair| pair.dup }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rank  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def inspect  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[@ranking, @solution].inspect  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sum = @@equation.dup  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;solution.each { |chr, num| sum.gsub!(chr, num.to\_s) }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"#{@@equation}\n#{sum}"  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> end  
> \</code\>
> 
> \<code\>  
> # lib/solver.rb  
> # Quiz 128  
> #  
> # Created by Morton Goldberg on 2007-06-18.  
> #  
> # Attempts to a solve cryptarithm puzzle by applying a Darwinian search  
> # (aka genetic algorithm). It can thought of as a stochastic breadth-first  
> # search. Although this method doesn't guarantee a solution will be  
> # found, it often finds one quite quickly.
> 
> MUTATION = 0.5  
> SWAP = 1.0
> 
> class Solver  
> &nbsp;&nbsp;&nbsp;&nbsp;attr\_reader :best, :population, :step  
> &nbsp;&nbsp;&nbsp;&nbsp;def initialize(pop\_size, fecundity, steps)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@pop\_size = pop\_size  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fecundity = fecundity  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@steps = steps  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mid\_step = steps / 2  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@step = 1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@population = []  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@pop\_size.times { @population \<\< Cryptarithm.new }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;select  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def run  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@steps.times do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;replicate  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;select  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break if @best.ranking.zero?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@step += 1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@best  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def replicate  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@pop\_size.times do |n|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;crypt = @population[n]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fecundity.times do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;child = crypt.dup  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;child.mutate if crypt.solution.size \< 10 && rand \<= MUTATION  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;child.swap if rand \<= SWAP  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@population \<\< child  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def select  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@population = @population.sort\_by { |crypt| crypt.rank }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@population = @population[0, @pop\_size]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@best = @population.first  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;def show  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if @step \> @steps  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"No solution found after #{step} steps"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Solution found after #{step} steps\n" + @best.to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> end  
> \</code\>
> 
> Regards, Morton

---

<div class="post-metadata">

**Author:** ![Andreas\_Launila](https://avatars.discourse-cdn.com/v4/letter/a/dbc845/32.png) [@Andreas\_Launila](https://rubytalk.org/u/Andreas_Launila)\
**Post date:** [19 June 2007 18:47 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/13 "2007-06-19T18:47:18Z")

</div>

Ruby Quiz wrote:

> This week's quiz is to build a program that reads in equations and outputs  
> solutions. You can decide how complex of an equation you want to support, with  
> the examples above being the minimum implementation.

This solution requires Gecode/R[1] 0.2.0 (need to install Gecode[3,4]  
followed by Gecode/R[2]). Gecode does the actual search, so we just  
specify the constraints. Two important aspects that are used to make the  
solution quick to compute are:

== Propagation rather than branching (deduction rather than exploration)

Branching (i.e. testing a guess, i.e. exploring the search space) costs  
since we have to save the old state and start a new one. Rather Gecode  
tries to prune as much of the search space as it can before resorting to  
exploration. In the case of linear constraints (e.g. a + 10\*b + c= 10\*d  
+ e, which corresponds to the problem

&nbsp;&nbsp;&nbsp;a  
+ bc

> **···**
>
> ----  
> &nbsp;&nbsp;de
> 
> ) it takes each variable and considers the smallest and largest values  
> it can possibly take. E.g. if we consider a in the above example we can  
> rewrite the linear equation to
> 
> a = 10\*(d - b) + e - c
> 
> From that equation we can check how large and small the right hand side  
> can be and then remove all other possibilities from the domain of a. We  
> can end up pruning quite a lot if we do this for each variable until  
> there are no more possibilities left to remove (coupled with the  
> distinct constraint).
> 
> In fact when we use this for send+more=money, without doing any sort of  
> exploration we can directly reduce the domains of the variables down to
> 
> s=9, e=4..7, n=5..8, d=2..8, m=1, o=0, r=2..8, y=2..8
> 
> So without any guessing at all we already know the value of three  
> letters and have pruned the domains of the others (i.e. pruned the  
> number of possibilities left, i.e. reduced the search space).
> 
> Since we now have to start exploring the search space we make a guess  
> that e=4. Propagating the constraints once again with e given the domain  
> 4 will directly result in a failure, so we backtrack and now know that  
> e!=4. With that information we redo the propagation and directly end up  
> at the solution with no need to explore any further. So in total we only  
> need to explore 4 out of 9^2\*10^6 nodes in the search space.
> 
> == Branching with a good heuristic
> 
> We don't randomly select where to go next in the search space, rather we  
> use a fail-first heuristic to try to cut down the search space faster.  
> The heuristic is to simply try to fixate a value for the variable with  
> the smallest domain. The reason that it works well is that we exhaust  
> domains quicker, hence forcing failures quicker (hence fail-first)
> 
> == The code
> 
> require 'rubygems'  
> require 'gecoder'
> 
> # Solves verbal arithmetic problems  
> # ( [Verbal arithmetic - Wikipedia](http://en.wikipedia.org/wiki/Verbal_arithmetic) ). Only supports  
> # addition.  
> class VerbalArithmetic \< Gecode::Model  
> &nbsp;&nbsp;# Creates a model for the problem where the left and right hand sides  
> &nbsp;&nbsp;# are given as an array with one element per term. The terms are given  
> &nbsp;&nbsp;# as strings  
> &nbsp;&nbsp;def initialize(lhs\_terms, rhs\_terms)  
> &nbsp;&nbsp;&nbsp;&nbsp;super()
> 
> &nbsp;&nbsp;&nbsp;&nbsp;# Set up the variables needed as a hash mapping the letter to its  
> &nbsp;&nbsp;&nbsp;&nbsp;# variable.  
> &nbsp;&nbsp;&nbsp;&nbsp;lhs\_terms.map!{ |term| term.split(//) }  
> &nbsp;&nbsp;&nbsp;&nbsp;rhs\_terms.map!{ |term| term.split(//) }  
> &nbsp;&nbsp;&nbsp;&nbsp;all\_terms = (lhs\_terms + rhs\_terms)  
> &nbsp;&nbsp;&nbsp;&nbsp;unique\_letters = all\_terms.flatten.uniq  
> &nbsp;&nbsp;&nbsp;&nbsp;letter\_vars = int\_var\_array(unique\_letters.size, 0..9)  
> &nbsp;&nbsp;&nbsp;&nbsp;@letters = Hash[\*unique\_letters.zip(letter\_vars).flatten!]
> 
> &nbsp;&nbsp;&nbsp;&nbsp;# Must satisfy the equation.  
> &nbsp;&nbsp;&nbsp;&nbsp;sum\_terms(lhs\_terms).must == sum\_terms(rhs\_terms)
> 
> &nbsp;&nbsp;&nbsp;&nbsp;# Must be distinct.  
> &nbsp;&nbsp;&nbsp;&nbsp;letter\_vars.must\_be.distinct
> 
> &nbsp;&nbsp;&nbsp;&nbsp;# Must not begin with a 0.  
> &nbsp;&nbsp;&nbsp;&nbsp;all\_terms.map{ |term| term.first }.uniq.each do |letter|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@letters[letter].must\_not == 0  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;# Select a branching, we go for fail first.  
> &nbsp;&nbsp;&nbsp;&nbsp;branch\_on letter\_vars, :variable =\> :smallest\_size, :value =\> :min  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;@letters.map{ |letter, var| "#{letter}: #{var.val}" }.join("\n")  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;private
> 
> &nbsp;&nbsp;# A helper to make the linear equation a bit tidier. Takes an array of  
> &nbsp;&nbsp;# variables and computes the linear combination as if the variable  
> &nbsp;&nbsp;# were digits in a base 10 number. E.g. x,y,z becomes  
> &nbsp;&nbsp;# 100\*x + 10\*y + z .  
> &nbsp;&nbsp;def equation\_row(variables)  
> &nbsp;&nbsp;&nbsp;&nbsp;variables.inject{ |result, variable| variable + result\*10 }  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;# Computes the sum of the specified terms (given as an array of arrays  
> &nbsp;&nbsp;# of characters).  
> &nbsp;&nbsp;def sum\_terms(terms)  
> &nbsp;&nbsp;&nbsp;&nbsp;rows = terms.map{ |term| equation\_row(@letters.values\_at(\*term)) }  
> &nbsp;&nbsp;&nbsp;&nbsp;rows.inject{ |sum, term| sum + term }  
> &nbsp;&nbsp;end  
> end
> 
> if ARGV.empty?  
> &nbsp;&nbsp;abort "Usage: #{$0} '\<word\_1\>+\<word\_2\>+...+\<word\_n\>=\<word\_res\>'"  
> end  
> lhs, rhs = ARGV[0].split('=').map{ |eq\_side| eq\_side.split('+') }  
> solution = VerbalArithmetic.new(lhs, rhs).solve!  
> if solution.nil?  
> &nbsp;&nbsp;puts 'Failed'  
> else  
> &nbsp;&nbsp;puts solution.to\_s  
> end
> 
> [1] [http://gecoder.rubyforge.org/](http://gecoder.rubyforge.org/)  
> [2] [http://gecoder.rubyforge.org/installation.html](http://gecoder.rubyforge.org/installation.html)  
> [3] [GECODE download](http://www.gecode.org/download.html)  
> [4] [http://www.gecode.org/gecode-doc-latest/PageComp.html](http://www.gecode.org/gecode-doc-latest/PageComp.html)
> 
> --  
> Andreas Launila

---

<div class="post-metadata">

**Author:** ![Bob\_Showalter1](https://avatars.discourse-cdn.com/v4/letter/b/7ab992/32.png) [@Bob\_Showalter1](https://rubytalk.org/u/Bob_Showalter1)\
**Post date:** [20 June 2007 13:13 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/14 "2007-06-20T13:13:44Z")

</div>

Here's my solution: [http://pastie.caboo.se/72030](http://pastie.caboo.se/72030)

It's a brute-force search, but has reasonable speed. +, -, and \*  
operators are supported.

> **···**
>
> On 6/15/07, Ruby Quiz \<james@grayproductions.net\> wrote:
> 
> > This week's quiz is to build a program that reads in equations and outputs  
> > solutions. You can decide how complex of an equation you want to support, with  
> > the examples above being the minimum implementation.

---

<div class="post-metadata">

**Author:** ![James\_Edward\_Gray\_II](https://avatars.discourse-cdn.com/v4/letter/j/ea5d25/32.png) [@James\_Edward\_Gray\_II](https://rubytalk.org/u/James_Edward_Gray_II)\
**Post date:** [15 June 2007 12:57 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/15 "2007-06-15T12:57:34Z")

</div>

Well, a non-clever way is to try an exhaustive search of each digit for each letter.

James Edward Gray II

> **···**
>
> On Jun 15, 2007, at 7:55 AM, anansi wrote:
> 
> > I really don't get it 🙂 How did you figured out that 2 = y in your example?

---

<div class="post-metadata">

**Author:** ![darren\_kirby](https://avatars.discourse-cdn.com/v4/letter/d/ed8c4c/32.png) [@darren\_kirby](https://rubytalk.org/u/darren_kirby)\
**Post date:** [17 June 2007 00:08 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/16 "2007-06-17T00:08:41Z")

</div>

quoth the Aureliano Calvo:

> When can I send my solution to be  
> reviewed?

I think about 12 hours from now...

-d

> **···**
>
> --  
> darren kirby :: Part of the problem since 1976 :: [http://badcomputer.org](http://badcomputer.org)  
> "...the number of UNIX installations has grown to 10, with more expected..."  
> - Dennis Ritchie and Ken Thompson, June 1972

---

<div class="post-metadata">

**Author:** ![Aureliano\_Calvo](https://avatars.discourse-cdn.com/v4/letter/a/f04885/32.png) [@Aureliano\_Calvo](https://rubytalk.org/u/Aureliano_Calvo)\
**Post date:** [17 June 2007 15:11 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/17 "2007-06-17T15:11:42Z")

</div>

Well, I think I can send it now. My implementation is on  
[http://pastie.caboo.se/71198\](http://pastie.caboo.se/71198%5C).

> **···**
>
> On 6/16/07, Aureliano Calvo \<aurelianocalvo@gmail.com\> wrote:
> 
> > Mi implementation is "working". For instance:
> > 
> > ./quiz\_128.rb "a+b=c"  
> > a: 5  
> > b: 1  
> > c: 6
> > 
> > But  
> > ./quiz\_128.rb "send+more=money" is taking ages!
> > 
> > time ./quiz\_128.rb "send+more=money"  
> > m: 1  
> > y: 2  
> > n: 6  
> > o: 0  
> > d: 7  
> > e: 5  
> > r: 8  
> > s: 9
> > 
> > real 7m17.065s  
> > user 2m46.806s  
> > sys 0m6.676s
> > 
> > Is there some trick to cut the solution space (I'm just scanning the  
> > solution space) ? I know I have a crappy PC but I have a feeling that  
> > this is not the real problem! When can I send my solution to be  
> > reviewed?
> > 
> > \> The three rules of Ruby Quiz:  
> > \>  
> > \> 1. Please do not post any solutions or spoiler discussion for this quiz until  
> > \> 48 hours have passed from the time on this message.  
> > \>  
> > \> 2. Support Ruby Quiz by submitting ideas as often as you can:  
> > \>  
> > \> [http://www.rubyquiz.com/](http://www.rubyquiz.com/)  
> > \>  
> > \> 3. Enjoy!  
> > \>  
> > \> Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone  
> > \> on Ruby Talk follow the discussion. Please reply to the original quiz message,  
> > \> if you can.  
> > \>  
> > \> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=  
> > \>  
> > \> A famous set of computer problems involve verbal arithmetic. In these problems,  
> > \> you are given equations of words like:  
> > \>  
> > \> send  
> > \> + more  
> > \> ------  
> > \> money  
> > \>  
> > \> or:  
> > \>  
> > \> forty  
> > \> ten  
> > \> + ten  
> > \> -------  
> > \> sixty  
> > \>  
> > \> The goal is to find a single digit for each letter that makes the equation true.  
> > \> Normal rules of number construction apply, so the first digit of a multi-digit  
> > \> number should be nonzero and each letter represents a different digit.  
> > \>  
> > \> This week's quiz is to build a program that reads in equations and outputs  
> > \> solutions. You can decide how complex of an equation you want to support, with  
> > \> the examples above being the minimum implementation.  
> > \>  
> > \> Here's a solution you can test against:  
> > \>  
> > \> $ ruby verbal\_arithmetic.rb 'send+more=money'  
> > \> s: 9  
> > \> e: 5  
> > \> n: 6  
> > \> d: 7  
> > \> m: 1  
> > \> o: 0  
> > \> r: 8  
> > \> y: 2  
> > \>
> > 
> > --  
> > "Es también nuestra intención erradicar la corrupción, ofreciendo como  
> > norma la honestidad, la idoneidad y la eficiencia. Con madurez y  
> > sentido de unidad es fácil pensar en la recomposición del ser  
> > argentino. Ese ser argentino, basado en madurez y en sentido de  
> > unidad, permitirá inspirar para elevarnos por encima de la miseria que  
> > la antinomia nos ha planteado, para dejar, de una vez por todas, ese  
> > ser "anti" y ser, de una vez por todas, "pro": "Pro argentinos""
> > 
> > Jorge Rafael Videla para el 25 de mayo de 1976
> 
> --  
> "Es también nuestra intención erradicar la corrupción, ofreciendo como  
> norma la honestidad, la idoneidad y la eficiencia. Con madurez y  
> sentido de unidad es fácil pensar en la recomposición del ser  
> argentino. Ese ser argentino, basado en madurez y en sentido de  
> unidad, permitirá inspirar para elevarnos por encima de la miseria que  
> la antinomia nos ha planteado, para dejar, de una vez por todas, ese  
> ser "anti" y ser, de una vez por todas, "pro": "Pro argentinos""
> 
> Jorge Rafael Videla para el 25 de mayo de 1976

---

<div class="post-metadata">

**Author:** ![Holger](https://avatars.discourse-cdn.com/v4/letter/h/ed8c4c/32.png) [@Holger](https://rubytalk.org/u/Holger)\
**Post date:** [17 June 2007 17:17 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/18 "2007-06-17T17:17:45Z")

</div>

Hello,

here is my solution for the arithmetic quiz. I am a ruby beginner and this  
is my first ruby program (ok, my second one, the first was "puts 'hello  
world'").  
So please forgive me the dirty trick using eval (why build an own parser if  
ruby provides such a fine solution?). I build in a brute force search  
algorithm  
via an iterative method for permutations. Therefore, any valid ruby  
expression is allowed, e.g.

verbal\_arithmetic.rb 'a+b==c && a+c==d-b && a\*c==d'  
solving a\*b\*c\*d!=0 && a+b==c && a+c==d-b && a\*c==d  
-- Solution ---  
b: 1  
c: 3  
d: 6

Please feel free to send any comments.

Regards  
Holger

#!/usr/bin/ruby -w

> **···**
>
> a: 2
> 
> #  
> # Solution to ruby quiz #128  
> # [http://www.rubyquiz.com/quiz128.html](http://www.rubyquiz.com/quiz128.html)  
> # by Holger  
> #  
> # Usage:  
> # verbal\_arithmetic.rb \<equation\>  
> #  
> # Examples:  
> # verbal\_arithmetic.rb 'send+more=money'  
> # verbal\_arithmetic.rb 'a+b==c && a+c==d-b && a\*c==d'  
> #
> 
> #\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*  
> # Permutator which gives all combinations of \<m\> elements out of  
> # array \<n\>  
> #  
> # usage:  
> # perms(m, n) { |x| ... }  
> #  
> #\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
> 
> def perms(m, n)  
> &nbsp;&nbsp;p = [nil] \* m  
> &nbsp;&nbsp;t = [-1] \* m  
> &nbsp;&nbsp;k = 0  
> &nbsp;&nbsp;while k \>= 0  
> &nbsp;&nbsp;&nbsp;&nbsp;if k==m  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yield p  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;k = k-1  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;n[t[k]] = p[k] if t[k]\>=0  
> &nbsp;&nbsp;&nbsp;&nbsp;while(t[k]\<n.length())  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;t[k] = t[k]+1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if n[t[k]]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p[k] = n[t[k]]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;n[t[k]] = nil  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;k = k+1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;t[k] = -1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;k = k-1 if t[k]==n.length()  
> &nbsp;&nbsp;end  
> end
> 
> # Read from command line and make valid ruby expression (= -\> == if not  
> already present)  
> puzzle = ARGV[0].gsub(/=+/,"==")
> 
> # Extract all letters and all first letters  
> digits = puzzle.gsub(/\W/,"").split(//).uniq  
> starts = puzzle.gsub(/(\w)\w\*|\W/,"\\1").split(//).uniq
> 
> if digits.length()\>= 10  
> &nbsp;&nbsp;puts "oops, too much letters"  
> else  
> &nbsp;&nbsp;# String containing all digits  
> &nbsp;&nbsp;digitss = digits.join
> 
> &nbsp;&nbsp;# Build "first digit must not be zero" condition  
> &nbsp;&nbsp;cond0 = starts.join("\*") + "!=0"
> 
> &nbsp;&nbsp;# And now perform an exhaustive search  
> &nbsp;&nbsp;puts "solving #{cond0} && #{puzzle}"
> 
> &nbsp;&nbsp;perms(digits.length(), (0...10).to\_a) { |v|  
> &nbsp;&nbsp;&nbsp;&nbsp;p0 = [cond0.tr](http://cond0.tr)(digitss, v.join)  
> &nbsp;&nbsp;&nbsp;&nbsp;p1 = [puzzle.tr](http://puzzle.tr)(digitss, v.join)  
> &nbsp;&nbsp;&nbsp;&nbsp;if eval(p0) && eval(p1) # Hint: first evaluate p0 as p1 may not be a  
> valid expression  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts '-- Solution ---'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[digits, v].transpose.each do |x,y| puts "#{x}: #{y}" end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;}  
> end

---

<div class="post-metadata">

**Author:** ![James\_Edward\_Gray\_II](https://avatars.discourse-cdn.com/v4/letter/j/ea5d25/32.png) [@James\_Edward\_Gray\_II](https://rubytalk.org/u/James_Edward_Gray_II)\
**Post date:** [18 June 2007 23:59 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/19 "2007-06-18T23:59:49Z")

</div>

Mine was too:

#!/usr/bin/env ruby -wKU

EQUATION = ARGV.shift.to\_s.downcase.sub("=", "==")  
LETTERS = EQUATION.scan(/[a-z]/).uniq  
CHOICES = LETTERS.inject(Hash.new) do |all, letter|  
&nbsp;&nbsp;&nbsp;all.merge(letter =\> EQUATION =~ /\b#{letter}/ ? 1..9 : 0..9)  
end

def search(choices, mapping = Hash.new)  
&nbsp;&nbsp;&nbsp;if choices.empty?  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;letters, digits = mapping.to\_a.flatten.partition { |e| e.is\_a? String }  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return mapping if eval(EQUATION.tr(letters.join, digits.join))  
&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new\_choices = choices.dup  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;letter = new\_choices.keys.first  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;digits = new\_choices.delete(letter).to\_a - mapping.values

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;digits.each do |choice|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if result = search(new\_choices, mapping.merge(letter =\> choice))  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return result  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return nil  
&nbsp;&nbsp;&nbsp;end  
end

if solution = search(CHOICES)  
&nbsp;&nbsp;&nbsp;LETTERS.each { |letter| puts "#{letter}: #{solution[letter]}" }  
else  
&nbsp;&nbsp;&nbsp;puts "No solution found."  
end

\_\_END\_\_

James Edward Gray II

> **···**
>
> On Jun 17, 2007, at 9:03 AM, Raf Coremans wrote:
> 
> > Here's my solution: [http://pastie.caboo.se/71188](http://pastie.caboo.se/71188)
> > 
> > It's of the dumb-brute-force-slow-as-hell variety:

---

<div class="post-metadata">

**Author:** ![Andreas\_Launila](https://avatars.discourse-cdn.com/v4/letter/a/dbc845/32.png) [@Andreas\_Launila](https://rubytalk.org/u/Andreas_Launila)\
**Post date:** [19 June 2007 22:33 UTC](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379/20 "2007-06-19T22:33:39Z")

</div>

Andreas Launila wrote:

> Since we now have to start exploring the search space we make a guess  
> that e=4. Propagating the constraints once again with e given the domain  
> 4 will directly result in a failure, so we backtrack and now know that  
> e!=4. With that information we redo the propagation and directly end up  
> at the solution with no need to explore any further. So in total we only  
> need to explore 4 out of 9^2\*10^6 nodes in the search space.

The last part is probably a bit misleading/incorrect. We are not  
directly exploring the space of all possible assignments when we branch,  
so saying that we have explored 4 nodes in that space is incorrect (i.e.  
we have not just explored 4 possible assignments, but we have visited  
just 4 nodes in our search space).

> **···**
>
> --  
> Andreas Launila

[Next page](https://rubytalk.org/t/quiz-verbal-arithmetic-128/38379.md?page=2)
