Enigmatic Code

Programming Enigma Puzzles

Enigma 1121: Families

From New Scientist #2277, 10th February 2001 [link]

There are six families, each consisting of mother, father and child. The mothers are Amber, Barbara, Christine, Dorothy, Ellen and Frances; the fathers are George, Harry, Inderjit, James, Kenneth and Lewis; the children are Matthew, Naomi, Oliver, Peter, Quentin and Rachel. The other day, everyone kept a diary of who they met in the 24-hour period; of course, everyone met the other two members of their family, but they also met other people. These are the diary records, given by initials:

A met G, J, L, M, N, P;
B met H, J, K, O, P, R;
C met I, K, L, M, O, Q;
D met G, I, L, P, Q, R;
E met H, I, K, M, N, R;
F met G, H, J, N, O, Q.

Also:

G met M, N, P;
H met O, P, R;
I met M, O, Q;
J met N, O, Q;
K met M, N, R;
L met P, Q, R.

If I told you who the wife of Inderjit is, then you could not work out who the father of Oliver is.

Question 1: Who is the wife of Inderjit?

If I told you who the mothers of Oliver and Quentin are, then you could work out who the mother of Peter is.

Question 2: Who are the mothers of Oliver, Quentin and Peter?

[enigma1121]

Advertisements

2 responses to “Enigma 1121: Families

  1. Jim Randell 3 April 2017 at 8:24 am

    This Python program make use of the filter_unique() function from the enigma.py library. It runs in 78ms

    from itertools import permutations
    from enigma import filter_unique, join, printf
    
    mothers = 'ABCDEF'
    fathers = 'GHIJKL'
    kids = 'MNOPQR'
    
    met1 = {
      'A': 'GJLMNP',
      'B': 'HJKOPR',
      'C': 'IKLMOQ',
      'D': 'GILPQR',
      'E': 'HIKMNR',
      'F': 'GHJNOQ',
    }
    
    met2 = {
      'G': 'MNP',
      'H': 'OPR',
      'I': 'MOQ',
      'J': 'NOQ',
      'K': 'MNR',
      'L': 'PQR',
    }
    
    r = list()
    
    # choose pairings of father and mother
    for fs in permutations(fathers):
      # check each pair met
      if not all(f in met1[m] for (m, f) in zip(mothers, fs)): continue
    
      # and assign the children
      for ks in permutations(kids):
        # check both the mother and father met their child
        if not all(k in met1[m] and k in met2[f] for (m, f, k) in zip(mothers, fs, ks)): continue
    
        # record possible orderings for fathers and children
        r.append((fs, ks))
    
    # the wife of I is...
    fn1a = lambda (fs, ks): mothers[fs.index('I')]
    # father of O is...
    fn1b = lambda (fs, ks): fs[ks.index('O')]
    
    # find solutions where knowing the wife of I does not give the father of O
    (_, q1) = filter_unique(r, fn1a, fn1b)
    
    # output the families
    families = lambda ms, fs, ks: join(map(join, zip(mothers, fs, ks)), sep=", ")
    
    # answer to question 1
    printf("[question 1]")
    for (fs, ks) in q1:
      wI = fn1a((fs, ks))
      printf("wife of I = {wI} [{s}]", s=families(mothers, fs, ks))
    printf()
    
    # the mothers of O and Q ...
    fn2a = lambda (fs, ks): (mothers[ks.index('O')], mothers[ks.index('Q')])
    # the mother of P...
    fn2b = lambda (fs, ks): mothers[ks.index('P')]
    
    # find solutions where knowing the mothers of O and Q does give the mother of P
    (q2, _) = filter_unique(q1, fn2a, fn2b)
    
    # answer to question 2
    printf("[question 2]")
    for (fs, ks) in q2:
      ((mO, mQ), mP) = (fn2a((fs, ks)), fn2b((fs, ks)))
      printf("mother of O = {mO}, mother of Q = {mQ}, mother of P = {mP} [{s}]", s=families(mothers, fs, ks))
    printf()
    

    Solution: 1. Christine is the wife of Inderjit; 2. Barbara is the mother of Oliver, Dorothy is the mother of Quentin, Amber is the mother of Peter.

    If we use all the information provided we can fully determine the families:

    Amber & George, Peter
    Barbara & Harry, Oliver
    Christine & Inderjit, Matthew
    Dorothy & Lewis, Quentin
    Ellen & Kenneth, Rachel
    Frances & James, Naomi

    For the first part there are 17 possible family groupings. (The pairings of mothers and fathers are the same in all cases).

    For the second part I assumed the possible family groupings had already been restricted by the information given for the first part, which gives the unique groupings given above. But if we treat it as a question independent of the information given in question 1 then we get the same answer, but there are two possible groupings that achieve it.

    We can adjust the program to solve this variant by passing r to filter_unique() in line 65 instead of q1.

    Another interpretation of the problem is if in the second part we are told the mothers of O and Q, but not told which child belongs to which parent. We can adjust the return value of fn2a in line 60 to return an unordered pair. If we do this we find there are no additional solutions.

    The program is written for Python 2, but can easily be adapted to run under either Python 2 or Python 3 by using the unpack() function (provided in the enigma.py library) in the definition of the functions in lines 42, 44, 60, 62.

  2. Brian Gladman 4 April 2017 at 10:58 am
    from itertools import permutations
    from collections import defaultdict
    
    mothers, fathers, children = 'ABCDEF', 'GHIJKL', 'MNOPQR'
    
    meet1 = { 'A': 'GJLMNP', 'B': 'HJKOPR', 'C': 'IKLMOQ',
              'D': 'GILPQR', 'E': 'HIKMNR', 'F': 'GHJNOQ' }
    
    meet2 = { 'G': 'MNP', 'H': 'OPR', 'I': 'MOQ',
              'J': 'NOQ', 'K': 'MNR', 'L': 'PQR' }
    
    # compile a list of possible sets of six families
    sol = []
    # permute the fathers who partner with the mothers
    for p in permutations(fathers):
      # ... who must all have met their partners
      if all(f in meet1[m] for m, f in zip(mothers, p)):
        
        # permute the children who are with each mother and father
        for q in permutations(children):
          # ... who must all have met both their parents
          if all(c in meet1[m] for m, c in zip(mothers, q)):
            if all(c in meet2[f] for f, c in zip(p, q)):
              
              # store solutions in a list of lists of the six families
              sol.append(list(m + f + c for m, f, c in zip(mothers, p, q)))
              
    print(f'There are {len(sol)} possible family arrangements.')
    
    # maps for mother to father and mothers to mother
    m2f, mm2m = defaultdict(set), defaultdict(set)
    # for each possible set of six families
    for f8 in sol:
      m_oq = ''
      for mfc in f8:
        # if I is in a family, store his partner
        if 'I' in mfc:
          w_i = mfc[0] 
        # if O is in a family, store his mother and father
        if 'O' in mfc:
          f_o = mfc[1]
          m_oq += mfc[0]
        # if Q is in a family, store his mother
        if 'Q' in mfc:
          m_oq += mfc[0]
        # if P is in a family, store his mother
        if 'P' in mfc:
          m_p = mfc[0]
      # map I's patner to possible fathers for O
      m2f[w_i].add(f_o)
      # map combinations of O and Q's mothers to
      # Peter's possible mothers 
      mm2m[tuple(sorted(m_oq))].add(m_p)
    
    # find situations in which knowing I's partner
    # does not reveal O's father
    for m, ff in m2f.items():
      if len(ff) > 1:
        print(f"I's wife is {m}", end=', ')
    
    # find situations in which knowing O and Q's
    # mothers reveals P's mother
    for m_oq, m in mm2m.items():
      if len(m) == 1:
        print("P's mother is {}.".format(*m))
    

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: