# Enigmatic Code

Programming Enigma Puzzles

## Enigma 1695: Odd one out

From New Scientist #2862, 28th April 2012 [link]

In the following list of five numbers I have consistently replaced digits by letters, with different letters being used for different digits: CAST THE ODD ONE OUT.

All but one of those five numbers is a perfect square. What is the numerical value of the odd one out?

Enigma 1613 is also called “Odd one out”.

[enigma1695]

### 10 responses to “Enigma 1695: Odd one out”

1. Jim Randell 25 April 2012 at 6:40 pm

Here’s my first attempt in Python. It’s not very quick though, it takes 4.5s under PyPy. I shall see if I can come up with a more efficient version later.

```from itertools import permutations
from enigma import concat, printf

# squares
squares3 = set(pow(n, 2) for n in range(10, 32))
squares4 = set(pow(n, 2) for n in range(32, 100))

# three digit numbers
words3 = ('THE', 'ODD', 'ONE', 'OUT')

# what are the letters in them?
letters3 = list(set().union(*words3))

# and the remaining letters in the 4-digit number
letters4 = set('CAST').difference(letters3)

# find an assignment that makes at least 3 of the three digit numbers squares
digits = set('0123456789')
for p3 in permutations(digits, len(letters3)):
d = dict(zip(letters3, p3))
if d['O'] == 0 or d['T'] == 0: continue

numbers = list(int(concat(*(d[l] for l in w))) for w in words3)
s3 = squares3.intersection(numbers)
n = len(s3)
if n < 3: continue

# and assign the remaining letters
for p4 in permutations(digits.difference(p3), len(letters4)):
d.update(zip(letters4, p4))
if d['C'] == 0: continue

CAST = int(concat(*(d[l] for l in 'CAST')))
# if one of the 3 digit numbers is not square...
if n == 3:
# ... then CAST must be
if not CAST in squares4: continue
# find out which of the 3 digit numbers is not square
ns = set(numbers).difference(squares3).pop()
else:
# CAST can't be square
if CAST in squares4: continue
ns = CAST

printf("{ns} [CAST={CAST} THE={numbers[0]} ODD={numbers[1]} ONE={numbers[2]} OUT={numbers[3]}]")
```

Solution: The odd number out is 185.

The non-square is OUT, with value 185.

The remaining numbers are: CAST = 3025 (55²), THE = 576 (24²), ODD = 144 (12²), ONE = 196 (14²).

• Naim Uygun 25 April 2012 at 8:48 pm

Here is my program:

```from  itertools import permutations
p=[100,121,144,169,196,225,256,289,324,361,400,441,484,529,576,625,676,729,784,841,900,961,1024,1089,1296,1369,1764,1849,1936,2025,2304,2401,2601,2704,2809,2916,3025,3249,3481,3721,4096,4356,4624,4761,5041,5184,5329,5476,5929,6084,6241,6561,6724,7056,7396,7569,7921,8464,8649,9025,9216,9409,9604,9801]
say=0
for w in permutations("1234567890"):

c=int(w[0])
a=int(w[1])
s=int(w[2])
t=int(w[3])
h=int(w[4])
e=int(w[5])
o=int(w[6])
d=int(w[7])
n=int(w[8])
u=int(w[9])
if c==0 or t==0 or o==0: continue
cast=1000*c+100*a+10*s+t
the=100*t+10*h+e
odd=100*o+10*d+d
one=100*o+10*n+e
out=100*o+10*u+t

say=0
if cast in p: say=say+1
if the in p : say=say+1
if odd in p: say=say+1
if one in p : say=say+1
if out in p : say=say+1
if say !=4 : continue
print(say)
print(cast,the,odd,one,out)
input("break")
```
• Jim Randell 25 April 2012 at 9:53 pm

OK, here’s a more efficient version. It runs in 246ms.

```from itertools import permutations
from enigma import concat, printf

# squares
squares3 = set(str(pow(n, 2)) for n in range(10, 32))
squares4 = set(str(pow(n, 2)) for n in range(32, 100))

# three digit numbers
words3 = set(('THE', 'ODD', 'ONE', 'OUT'))

# make a mapping from letters to digits
# (or None if it is not possible)
def mapping(letters, digits):
m = {}
for l, d in zip(letters, digits):
if l in m:
if m[l] != d: return None
else:
if d in m.values(): return None
m[l] = d
return m

# at least 3 of the 3 digit numbers must be squares
digits = set('0123456789')
for w in words3:
w3 = words3.difference((w,))
letters = concat(*w3)
# choose three of the 3 digit squares to go with them
for s3 in permutations(squares3, 3):
# and check them for consistency
m = mapping(letters, concat(*s3))
if m is None: continue
# which letter is unassigned?
l = set(w).difference(m.keys()).pop()
# assign it
for d in digits.difference(m.values()):
m[l] = d
# and check the corresponding number
n = concat(*(m[i] for i in w))
ns = None if n in squares3 else n
# now assign the remaining letters
for C, A, S in permutations(digits.difference(m.values()), 3):
CAST = concat(C, A, S, m['T'])
# if one of the 3 letter words is not square, then CAST must be
if ns is not None:
if not CAST in squares4: continue
else:
# CAST can't be square
if CAST in squares4: continue
ns = CAST

kw = dict((w, concat(*(m[i] for i in w))) for w in words3)
printf("{ns} [CAST={CAST} THE={THE} ODD={ODD} ONE={ONE} OUT={OUT}]", **kw)
```
• Jim Randell 23 March 2017 at 10:08 am

And here’s a version that uses the SubstitutedExpression() solver from the enigma.py library (which didn’t exist when the puzzle was originally posted).

It considers the problem as 5 different alphametic puzzles, where each one considers a different candidate word as being the non-square. It runs in 102ms.

```from enigma import SubstitutedExpression, sprintf as f, join

# words
words = ('CAST', 'THE', 'ODD', 'ONE', 'OUT')

# choose the word that is not a square
for w in words:
# create an alphametic puzzle
exprs = list(f("{z}is_square({x})", z=('not ' if x == w else '')) for x in words)
p = SubstitutedExpression(exprs) # add [[ d2i={} ]] if leading zeros are allowed
# and check for solutions
for s in p.solve(verbose=0):
# value of the non-square
v = p.substitute(s, w)
# and the remaining words
t = join((f("{x} = {y}", y=p.substitute(s, x)) for x in words if x != w), sep=", ")
print(f("non-square is {w} = {v} [{t}]"))
```
2. Brian Gladman 25 April 2012 at 10:04 pm

And here’s yet one more

```
from __future__ import print_function
from itertools import permutations

words = ( 'CAST', 'THE',  'ODD',  'ONE', 'OUT' )
sq = dict( (x * x, x) for x in range(10, 100) )

# permute the numbers to be used in the last three words
for p in permutations('0123456789', 6):
d = dict(zip('DENOTU', p))
# compute their values for this permutation
l3 = [int(''.join(d[c] for c in w)) for w in words[2:]]
# and check that there are two or more squares
if len([x for x in l3 if x in sq]) > 1:
# now permute the remaining digit values
for q in permutations(set('0123456789') - set(p)):
# and assign to the remaining letters
d.update(zip('ACHS', q))
# compile full list of word values
l5 = l3 + [int(''.join(d[c] for c in w)) for w in words[:2]]
# and find all non square values
l = [x for x in l5 if x not in sq]
if len(l) == 1:
print(l[0], sorted(l5))
```
3. arthurvause 6 May 2012 at 8:45 am

It is easy to deduce that the non-square has to be one of ODD, ONE or OUT, so I intended the code to test all these cases, but got lucky by finding that OUT is the non-square. The code runs in 35ms

```sq3 = {}
for s in [str(i*i) for i in range(10,32)] :
if s[0]<>s[1] and s[0]<>s[2] :
sq3[s]=set(s)

sq4 = {}
for s in [str(i*i) for i in range(32,100)] :
if len(set(s))==4 :
sq4[s]=set(s)

for THE in sq3:
if len(sq3[THE])==3:
for ODD in sq3:
if len(sq3[ODD])==2 :
for ONE in sq3:
if len(sq3[ONE])==3 and ONE[0]==ODD[0] and ONE[2]==THE[2] and len(sq3[THE] | sq3[ODD] | sq3[ONE])==6:
for CAST in sq4 :
if len(sq3[THE] | sq3[ODD] | sq3[ONE] | sq4[CAST])==9 and CAST[3]==THE[0]:
print CAST, THE, ODD, ONE
```
• arthurvause 6 May 2012 at 9:44 am

Code tidied up a bit :

```start = time()
sq3 = {}
for s in [str(i*i) for i in range(10,32)] :
if s[0]<>s[1] and s[0]<>s[2] :
sq3[s]=set(s)

sq4 = {}
for s in [str(i*i) for i in range(32,100)] :
if len(set(s))==4 :
sq4[s]=set(s)

for THE in [i for i in sq3 if len(sq3[i])==3 ]:
for ODD in [i for i in sq3 if len(sq3[i])==2 ]:
for ONE in [i for i in sq3 if len(sq3[i])==3 and i[0]==ODD[0] and i[2]==THE[2]]:
if len(sq3[THE] | sq3[ODD] | sq3[ONE])==6:
for CAST in [i for i in sq4 if i[3]==THE[0] ] :
if len(sq3[THE] | sq3[ODD] | sq3[ONE] | sq4[CAST])==9:
print CAST, THE, ODD, ONE

```
4. geoffrounce 25 February 2018 at 7:30 pm

Printing out the values of the five numbers enabled the non – square number to be identified.
Would have been better if the answer could have been found as a totally programmatic solution, but not sure if this is possible in MiniZinc.
@Jim: Any ideas?

```% A Solution in MiniZinc
include "globals.mzn";

var 0..9: C; var 0..9: A; var 0..9: S; var 0..9: T;
var 0..9: H; var 0..9: E; var 0..9: O; var 0..9: D;
var 0..9: N; var 0..9: U;

constraint C != 0 /\ T != 0 /\ O != 0;

constraint all_different ( [C, A, S, T, H, E, O, D, N, U] );

var 100..999: THE = 100*T + 10*H + E;
var 100..999: ODD = 100*O + 11*D;
var 100..999: ONE = 100*O + 10*N + E;
var 100..999: OUT = 100*O + 10*U + T;
var 1000..9999: CAST = 1000*C + 100*A + 10*S + T;

set of int: sq3 = {n*n | n in 10..31} ;
set of int: sq4 = {n*n | n in 32..99} ;

constraint sum (  [THE in sq3, ODD in sq3, ONE in sq3,
OUT in sq3, CAST in sq4]  ) = 4;

solve satisfy;

output  [" THE = " ++ show(THE) ++ "\n" ] ++
[" ODD = " ++ show(ODD) ++ "\n" ] ++
[" ONE = " ++ show(ONE) ++ "\n" ] ++
[" OUT = " ++ show(OUT) ++ "\n" ] ++
[" CAST = " ++ show(CAST) ++ "\n" ];

%  THE = 576  ODD = 144   ONE = 196
%  OUT = 185   -  Answer
%  CAST = 3025
%  ----------
% Finished in 129msec
```
• Jim Randell 25 February 2018 at 10:16 pm

```var int: non_square;

constraint not(CAST in sq4) -> non_square = CAST;
constraint not(THE in sq3) -> non_square = THE;
constraint not(ODD in sq3) -> non_square = ODD;
constraint not(ONE in sq3) -> non_square = ONE;
constraint not(OUT in sq3) -> non_square = OUT;
```

And have [[ "non-square = " ++ show(non_square) ]] in the output clause to get the solution displayed.

5. geoffrounce 26 February 2018 at 7:32 am

@jim: Thanks – that’s a neat solution to give a fully programmatic solution.