Brain-Teaser 928: Confused contracts
From The Sunday Times, 4th May 1980 [link]
The results of the hands in a bridge tournament were written like this: “Joe Smith, two ♥”. Before I could calculate the scores, my dog tore up the results of the last four players. Fortunately the writing remained legible. By manoeuvring the fragments, and knowing there had been one contract in each suit, and one at each of the one, two, three and four levels, I was able to construct various feasible combinations.
I first tried putting ♦ opposite Ted, Pete and Reg in turn. Ted could have ♦ only when Mr Green had the two contract; Pete only when ♠ were four; and Reg only when Mr Black had the one. Similarly, if Reg or Mr Black had ♣, then Mr Green had four; If Mr White had ♥ or ♣ then Mr Black had the other; if Reg had ♥ then Pete had ♦; if Mr Brown had ♥ then Mr Black had four; if Mr Black had ♥ then Mr Green had two; and if Mr Black didn’t have ♥ then ♠ were in less than four.
Mr Black could have one only when Pete had three ♠; Mr Green could have two only when ♥ were four; Mr Black had four only when Reg had ♦; Mr Green had four only when ♦ were in three; and ♣ could be in three or four only when Reg had ♥.
Finally I noted that Ted and Vic had the same coloured suits whenever I tried putting ♠ with the three, but different coloured suits if Mr White did not have ♥.
Luckily, I then remembered that ♦ had actually been bid at the four level.
What was the suit of the three contract, and what was the full name of the player who bid it?
This puzzle is included in the book The Sunday Times Book of Brainteasers (1994). The puzzle text above is taken from the book.
[teaser928]









Jim Randell 8:06 am on 10 September 2023 Permalink |
I know nothing of Bridge, so at first the puzzle text made no sense to me, but there is a handy hint in the 1974 book:
I used the [[
SubstitutedExpression]] solver to allocate the values among the three groups, so all we need to do is translate the text into a collection of constraints which we can represent by Python expressions.I translated: “If A had X or Y, then B had the other” into: “(A = X) ⇒ (B = Y)” and “(A = Y) ⇒ (B = X)”.
Note that we might expect to deduce “Reg != Black” from the statement “if Reg or Mr Black had …”, but if we try this then there are no solutions.
The following run file executes in 66ms. (Internal runtime of the generated code is 185µs).
Run: [ @replit ]
#! python3 -m enigma -rr # allocate values 1-4 to the following: # # suits : C D S H [Clubs, Diamonds, Spades, Hearts] # forenames: T R P V [Ted, Reg, Pete, Vic] # surnames : B K G W [Brown, blacK, Green, White] SubstitutedExpression --digits="1-4" --distinct="CDSH,TRPV,BKGW" # specify the conditions "implies(T == D, G == 2)" "implies(P == D, S == 4)" "implies(R == D, K == 1)" "implies(R == C or K == C, G == 4)" "implies(W == H, K == C)" "implies(W == C, K == H)" "implies(R == H, P == D)" "implies(B == H, K == 4)" "implies(K == H, G == 2)" "implies(K != H, S != 4)" "implies(K == 1, P == 3 == S)" "implies(G == 2, H == 4)" "implies(K == 4, R == D)" "implies(G == 4, D == 3)" "implies(C == 3 or C == 4, R == H)" "implies(S == 3, {T, V} in [{C, S}, {D, H}])" "implies(W != H, {T, V} not in [{C, S}, {D, H}])" # diamonds was the 4th contract --assign="D,4" # mention all the variables "true(C, D, S, H, T, R, P, V, B, K, G, W)" --template="(C D S H) (T R P V) (B K G W)" --solution=""Solution: The 3 contract was in hearts. It was bid by Pete Green.
The contracts are fully defined:
And we see that Reg = Black.
LikeLike
Frits 4:06 pm on 11 September 2023 Permalink |
More than 200 lines of code.
from itertools import combinations # allocate values 1-4 to the following: # # suits : C D S H [Clubs, Diamonds, Spades, Hearts] # forenames: T R P V [Tom, Reg, Pete, Vic] # surnames : B K G W [Brown, blacK, Green, White] # 0/1/2 for no/some/all details detail = 1 rev = lambda x: "neq" if x == "eq" else "eq" # a specific print format p_frz = lambda x, y: f"{x:>3}-{''.join(sorted(y))}" # add implication if <x> then <y> def implies(x, y): if "=" in x: op1 = "eq" if "==" in x else "neq" op2 = "eq" if "==" in y else "neq" # use frozenset to make it hashable var1, var2 = frozenset([x[0], x[-1]]), frozenset([y[0], y[-1]]) imp[(op1, var1)] = imp.get((op1, var1), []) + [(op2, var2)] # if a implies b then not b implies not a imp[(rev(op2), var2)] = imp.get((rev(op2), var2), []) + [(rev(op1), var1)] else: if x in imp: if y not in imp[x]: # check if y results in a falsehood if y == (rev(x[0]), x[1]): print(f"ERROR {x}-{y} is false") if y[1] in fb[x[1]] and x[0] == y[0] == "eq": if detail: print(f"{p_frz(x[0], x[1])} is false as it leads to " f"eq-{''.join(y[1])} which is not allowed") add_truth("neq", x[1]) imp[x] += [y] else: imp[x] = [y] # print implications def print_imp(d): print() for (a, b), vs in sorted(d.items()): print(f"{p_frz(a, b)} : ", end=' ') for (c, d) in vs: print(f"{p_frz(c, d)}", end=', ') print() print() # add entries to truth list <t> def add_truth(c, s): s_ = frozenset(s) if (c, s_) in t: return l_ = list(s) if detail: print("add to t:", p_frz(c, s_)) t.add((c, s_)) if c != "eq": return # add forbidden entries for f in fb[s_]: if detail > 1: print("add inherited rules to t:", p_frz("neq", f)) t.add(("neq", f)) toadd = [] # propagate 'eq-ab': if 'neq-ac' exists then also 'neq-bc' must be true for d, f in t: if d != "neq": continue for i, x in enumerate(l_): if x not in f: continue o, = f.difference([x]) if o not in bd[l_[1 - i]]: toadd.append(frozenset([l_[1 - i], o])) for x in toadd: add_truth("neq", x) imp, fb, bd = dict(), dict(), dict() # specify the conditions implies("T == D", "G == 2") implies("P == D", "S == 4") implies("R == D", "K == 1") implies("W == H", "K == C") implies("W == C", "K == H") implies("R == H", "P == D") implies("B == H", "K == 4") implies("K == H", "G == 2") implies("K != H", "S != 4") implies("K == 1", "P == 3") implies("K == 1", "P == S") implies("K == 1", "S == 3") implies("G == 2", "H == 4") implies("K == 4", "R == D") implies("G == 4", "D == 3") #implies("R == C or K == C", "G == 4") implies("R == C", "G == 4") implies("K == C", "G == 4") #implies("C == 3 or C == 4", "R == H") implies("C == 3", "R == H") implies("C == 4", "R == H") #implies("S == 3", "{T, V} in [{C, S}, {D, H}]") #implies("W != H", "{T, V} not in [{C, S}, {D, H}]") implies("S == 3", "W == H") types = [x.split() for x in ["C D S H", "T P R V", "B K G W", "1 2 3 4"]] # buddies bd = {x: [y for y in types[i] if y != x] for i, tp in enumerate(types) for x in tp} # forbidden values for a, b in combinations(bd.keys(), 2): if b in bd[a]: continue fb[frozenset([a, b])] = [frozenset([a, x]) for x in bd[b]] + \ [frozenset([b, x]) for x in bd[a]] if detail > 1: print("forbidden\n---------") for k, vs in sorted(fb.items()): print(''.join(k), end=" : ") for v in vs: print(''.join(v), end=", ") print() print() t = set() # diamonds was the 4th contract add_truth('eq', frozenset(['D', '4'])) loop, ln = 1, len(t) while True: if detail: print(f"\nloop {loop}\n------") if loop < 3: print_imp(imp) if detail > 1: print(f"\ntruths\n------") for x in sorted(t, key = lambda x: (x[0], sorted(x[1]) ) ): print(p_frz(x[0], ''.join(sorted(x[1])))) # expand implications by chaining for k, vs in imp.items(): for v in vs: # check if v is allowed ... if (rev(v[0]), v[1]) in t: # ... if not disallow k add_truth(rev(k[0]), k[1]) if v in imp: # add implications of v to k for i in imp[v]: implies(k, i) # check for 3 non-equalities in a group toadd = [] for c, f in t: if c != "neq": continue lst = list(f) # for both elements within f for i in range(2): y = [("neq", frozenset([x, lst[1-i]])) in t for x in bd[lst[i]]] if sum(y) != 2: continue # we have 3 neq's so the remaining entry in group must be eq toadd.append(frozenset([lst[1-i], bd[lst[i]][y.index(0)]])) for x in toadd: add_truth("eq", x) # only if normal logic has been exhausted if len(t) == ln: lookup = lambda d, x: [f for c, f in d if c == "eq" and x in f and len(f | set("1234")) == 5] # implies("S == 3", "{T, V} in [{C, S}, {D, H}]") # implies("W != H", "{T, V} not in [{C, S}, {D, H}]") s3 = ("eq", frozenset(["S", "3"])) in t ns3 = ("neq", frozenset(["S", "3"])) in t if not s3 and not ns3: break # D is 4 so try to find H tH = lookup(t, "H") if not tH: break tT, tV = lookup(t, "T"), lookup(t, "V") if not tV and not tT: break H, = tH[0].difference(["H"]) DH = {'4', H} vr, ot = "V" if tV else "T", "T" if tV else "V" v, = (tV[0] if tV else tT[0]).difference([vr]) n = DH.difference([v]).pop() if v in DH else (set("1234") - DH - {v}).pop() add_truth("neq" if ns3 else "eq", frozenset([ot, n])) r3 = [''.join(f.difference(["3"])) for c, f in t if c == "eq" and '3' in f] # is there enough data in row 3 for the answer? if len(r3) == 3: print(f"\nanswer: {r3[0]}, {r3[1]} and {r3[2]}") exit(0) ln = len(t) loop += 1LikeLike