The Oldest Plays the Piano

Two MIT math grads bump into each other while shopping at Fry’s. They haven’t seen each other in over 20 years.

First grad to the second: “How have you been?”
Second: “Great! I got married and I have three daughters now.”
First: “Really? How old are they?”
Second: “Well, the product of their ages is 72, and the sum of their ages is the same as the number on that building over there…”
First: “Right, ok… Oh wait… Hmm, I still don’t know.”
Second: “Oh sorry, the oldest one just started to play the piano.”
First: “Wonderful! My oldest is the same age!”

How old was the first grad’s daughter?

Solution

The possible ages ( factors of 72 ) and their sums are shown below:

Ages:            Sum of ages:
1 1 72 74
1 2 36 39
1 3 24 28
1 4 18 23
1 6 12 19
1 8 9 18
2 2 18 22
2 3 12 17
2 4 9 15
2 6 6 14
3 3 8 14
3 4 6 13

We can deduce from the man’s confusion over the building number that this wasn’t enough information to solve the problem. The chart shows the sum 14 twice for two different age possibilities, which would explain how knowing the building number alone would not have given him the answer. The clue that the “oldest one” started to play the piano rules out “2 6 6” as an answer, because there is no “oldest”. Since the
first grad was certain with the piano clue, the first grad’s oldest daughter is 8. I’ll leave it up to the reader to figure out why this doesn’t necessarily mean the second grad’s oldest daughter was also 8.

2026 Update: Logic Deduction Puzzles — Constraint Propagation

Deduction puzzles (“the oldest plays piano, the youngest has blue eyes…”) are essentially constraint satisfaction problems (CSPs). Learning to model them systematically leads to elegant solutions for complex variants.

from itertools import permutations

def solve_csp(variables, domains, constraints):
    """
    Generic backtracking CSP solver.
    variables: list of variable names
    domains: dict mapping variable → list of possible values
    constraints: list of functions (assignment → bool) that must hold
    """
    assignment = {}

    def is_consistent(var, value):
        test = {**assignment, var: value}
        return all(c(test) for c in constraints if all(v in test for v in c.__code__.co_varnames[:c.__code__.co_argcount]))

    def backtrack():
        if len(assignment) == len(variables):
            return assignment.copy()
        var = next(v for v in variables if v not in assignment)
        for value in domains[var]:
            if is_consistent(var, value):
                assignment[var] = value
                result = backtrack()
                if result:
                    return result
                del assignment[var]
        return None

    return backtrack()

# Example: Classic "who owns the fish" (Einstein's riddle) setup
# Simplified version with 3 siblings and 3 attributes
def oldest_piano_puzzle():
    """
    Three siblings: Alice, Bob, Carol (oldest to youngest).
    Instruments: piano, violin, guitar.
    Clues: oldest plays piano, youngest doesn't play violin.
    Find: who plays what?
    """
    siblings = ['Alice', 'Bob', 'Carol']  # Alice=oldest, Carol=youngest
    instruments = ['piano', 'violin', 'guitar']

    for perm in permutations(instruments):
        assignment = dict(zip(siblings, perm))
        # Clue 1: Oldest (Alice) plays piano
        if assignment['Alice'] != 'piano':
            continue
        # Clue 2: Youngest (Carol) doesn't play violin
        if assignment['Carol'] == 'violin':
            continue
        print(f"Solution: {assignment}")

oldest_piano_puzzle()
# Alice: piano, Bob: violin, Carol: guitar
# Alice: piano, Bob: guitar, Carol: violin  ← fails clue 2
# → Unique solution: Alice=piano, Bob=violin, Carol=guitar

# Systematic approach using arc consistency (AC-3)
def ac3_simplify(domains, constraints_by_pair):
    """
    AC-3 algorithm: enforce arc consistency before backtracking.
    Reduces domain sizes, often solves CSPs without backtracking.
    """
    from collections import deque
    queue = deque(constraints_by_pair.keys())
    while queue:
        (xi, xj) = queue.popleft()
        if revise(domains, xi, xj, constraints_by_pair[(xi, xj)]):
            if not domains[xi]:
                return False  # No solution
            for xk in [v for v in domains if v != xj]:
                queue.append((xk, xi))
    return True

def revise(domains, xi, xj, constraint):
    revised = False
    for val_i in domains[xi][:]:
        if not any(constraint(val_i, val_j) for val_j in domains[xj]):
            domains[xi].remove(val_i)
            revised = True
    return revised

Real-world CSP applications:

  • Scheduling: Assign time slots to meetings with conflict constraints (Google Calendar optimization)
  • Register allocation: Assign CPU registers to variables in compilers (graph coloring = CSP)
  • Sudoku: Each cell is a variable; rows, columns, and boxes are all-different constraints
  • N-Queens: No two queens attack each other = CSP with arc consistency reducing search space
Scroll to Top