# Enigmatic Code

Programming Enigma Puzzles

## Enigma 665: Occupational hazard

From New Scientist #1820, 9th May 1992 [link]

Alan, Brian and Charles have surnames Adams, Brown and Collins (not necessarily respectively) and occupations of architect, builder and carpenter (again not necessarily respectively). Each of them is either thoroughly honest or thoroughly dishonest. Below are some statements (not necessarily by more than one person) which involve these three people’s names and jobs: (each blank space originally contained one of the surnames — Mr Adams deleted the names after seeing the first few statements):

Alan says:

(i) I am not the architect.
(ii) Brian is a carpenter.
(iii) Charles’s surname is [……….].

(i) The architect’s surname is not Brown.
(ii) The builder’s surname is [………].
(iii) The two spaces contain the same surname.

The architect says:

(i) The builder isn’t called Charles.
(ii) It’s now possible to work out all our names and jobs.

Please state (in order) the [deleted] surnames.

If I’ve counted correctly there are now “only” 200 Enigma puzzles remaining to post. (Actually I think there are 196).

[enigma665]

### 3 responses to “Enigma 665: Occupational hazard”

1. Jim Randell 27 June 2022 at 10:26 am

This Python program runs in 65ms. (Internal run time is 219µs).

Run: [ @replit ]

```from enigma import (subsets, group, unpack, printf)

# always tells the truth
def T(s): return bool(s)

# never tells the truth
def F(s): return not(s)

# record candidate solutions
rs = list()

# assign behaviours for Alan, Brian, Charles
for fs in subsets((T, F), size=3, select="M"):
(fA, fB, fC) = fs

# assign jobs
for js in subsets("ABC", size=3, select="P"):
(jA, jB, jC) = js
jAi = js.index('A')
jBi = js.index('B')
arch = fs[jAi]

# Alan: "Alan is not the architect"
if not fA(jA != 'A'): continue

# Alan: "Brian is the carpenter"
if not fA(jB == 'C'): continue

# architect: "The builder isn't called Charles"
if not arch(jC != 'B'): continue

# assign surnames
for ss in subsets("ABC", size=3, select="P"):
(sA, sB, sC) = ss
sAi = ss.index('A')
sBi = ss.index('B')

# Mr Adams: "The architect's surname is not Brown"
if not adams(ss[jAi] != 'B'): continue

# choose two surnames for the blanks
for (b1, b2) in subsets("ABC", size=2, select="M"):

# Alan: "Charles' surname is {b1}"
if not fA(sC == b1): continue

# Mr Adams: "The builder's surname is {b2}"
if not adams(ss[jBi] == b2): continue

# Mr Adams: "The two blanks contain the same surname"
if not adams(b1 == b2): continue

# record the results
rs.append((fs, js, ss, b1, b2, arch))

# group solutions by jobs and surname
d = group(rs, by=unpack(lambda fs, js, ss, b1, b2, arch: (js, ss)))

# consider the solutions
for vs in d.values():
for (fs, js, ss, b1, b2, arch) in vs:
# architect says there is a single solution
if not arch(len(d.keys()) == 1): continue

# output solution
((fA, fB, fC), (jA, jB, jC), (sA, sB, sC)) = (fs, js, ss)
f = lambda x: x.__name__
printf("blanks = {b1}, {b2} [A={A} {sA} {jA}; B={B} {sB} {jB}; C={C} {sC} {jC}]", A=f(fA), B=f(fB), C=f(fC))
```

Solution: The deleted names are: “Brown” and “Adams”.

Alan Collins is the builder, he always tells the truth.

Brian Adams is the carpenter, he never tells the truth.

Charles Brown is the architect, he always tells the truth.

So:

Alan (Collins): “I am not the architect” (true); “Brian is the carpenter” (true); “Charles’ surname is Brown” (true)
(Brian) Adams: “The architect’s surname is not Brown” (false); “The builder’s surname is Adams” (false); “The two blanks contain the same surname” (false)
(Charles Brown) architect: “The builder isn’t called Charles” (true); “You can work out our names/jobs” (true)

2. Frits 30 June 2022 at 10:57 am

It is probably not possible to directly code the architect’s (ii) statement unless [SubstitutedExpression] supports a 2-pass mode.

```
from enigma import SubstitutedExpression

name     = {0: "Adams",     1: "Brown",    2: "Collins"}
job      = {0: "architect", 1: "builder",  2: "carpenter"}
speakers = {0: "Alan",      1: "Mr Adams", 2: "Architect"}

#            name  job
# Alan   :    D     G      A = Alan says:           X = first space
# Brian  :    E     H      B = Mr Adams says:       Y = second space
# Charles:    F     I      C = The architect says:

# the alphametic puzzle
p = SubstitutedExpression(
[
# ------------ Alan says:

# (i) I am not the architect.
# (ii) Brian is a carpenter.
# (iii) Charles's surname is [……….].
"(G != 0) = A",
"A == (H == 2) == (F == X)",

# (i) The architect's surname is not Brown.
# (ii) The builder's surname is [………].
# (iii) The two spaces contain the same surname.
"all(x != 1 for x, y in zip([D, E, F], [G, H, I]) if y == 0) = B",
"B == ([x for x, y in zip([D, E, F], [G, H, I]) if y == 1][0] == Y) \
== (X == Y)",

# ------------ The architect says:

# (i) The builder isn't called Charles.
# (ii) It's now possible to work out all our names and jobs.
"(I != 1) = C",         # we can't code (ii) yet

# ------------ cross checks

# if Alan is the architect then their statements must be coherent
"G != 0 or A == C",

# if Alan is Mr Adams then their statements must be coherent
"D != 0 or A == B",

# if Mr Adams is the architect then their statements must be coherent
"(0, 0) not in {(D, G), (E, H), (F, I)} or B == C",
],
answer="(D, G), (E, H), (F, I), (X, Y), (A, B, C)",
base=3,
distinct=("DEF","GHI"),
verbose=0,    # use 256 to see the generated code
)

d = dict()

# collect solutions
sols = [y for _, y in p.solve()]

# group solutions by jobs and surname
for s in sols:
d[s[0] + s[1] + s[2]] = s[3] + s[4]

# is it possible to work out all names and jobs
oneSolution = True if (len(d) == 1) else False

for s in sols:
# architect says there is a single solution
if oneSolution and s[4][2] == 0: continue
if not oneSolution and s[4][2] == 1: continue

print(f"Alan {name[s[0][0]]}, {job[s[0][1]]}")
print(f"Brian {name[s[1][0]]}, {job[s[1][1]]}")
print(f"Charles {name[s[2][0]]}, {job[s[2][1]]}")
print(", ".join(speakers[i] + ' always ' +
('speaks the truth' if x else 'lies') for i, x in enumerate(s[4])))
print()
print(f"the [deleted] surnames: {' and '.join(name[n] for n in set(s[3]))}")
```
• Jim Randell 2 July 2022 at 1:03 pm

Note that the use of `set()` means this program won’t necessarily print the correct answer on a Python implementation that does not preserve insertion order in sets. Which includes the standard CPython implementation. (Although I believe PyPy does preserve set insertion order).

This site uses Akismet to reduce spam. Learn how your comment data is processed.