← Projects

A Monte Carlo look at how to pick a restaurant

Exploration · Monte Carlo simulation · Summer 2023

Two people pick a restaurant by taking turns crossing options off a list. I was bored one weekend and wondered whether the way you take those turns actually changes who ends up happy, so I ran ten thousand of them in a Monte Carlo simulation. The order you make the cuts does matter a bit, mostly for the pickier of the two diners.

10,000
Simulated two-person dinners per setting
≈ 3×
Geometric’s edge over linear at two rounds, egalitarian measure (0.11 vs. 0.34 regret)
8 rounds
Where the two schedules finally tie

The setup

I blew the ritual up a bit to make any pattern easier to see: thirty candidate restaurants instead of a shortlist, and up to eight passes instead of the usual one or two. One diner knocks out the spots they’d hate and hands the survivors over, the other trims further, back and forth until a single restaurant is left.

In real life you get two or three passes before everyone’s too hungry to care, so the question isn’t which restaurant is best. It’s how to spend a scarce handful of rounds, and whether that changes as the diners get more room to haggle.

The model

I gave each of thirty restaurants two independent ratings, one per diner, drawn from a bell curve centered at 5. A joint pick can be judged two ways, and they pull in different directions:

For each trial I also computed the single best restaurant under each measure (what a perfectly informed referee would choose) and scored every strategy by its regret: how far below that ideal the actual pick landed. Lower is better; zero would mean the turn-taking found the optimum.

Two ways to narrow down

Every strategy uses the same machinery: on your turn you keep your top-k favorites and pass the trimmed list along. The only thing that changes is the schedule: how many candidates survive each successive round. I compared two:

Same starting list, same final pick of one, same number of rounds. Only the spacing of the cuts is different.

What the simulation says

Across 10,000 trials, here is the average regret for each schedule, broken out by how many rounds the diners take, and by which measure they’re optimizing:

Judge the pick by

Geometric — cut a constant fractionLinear — cut a constant number
Average regret below the best-possible restaurant (lower is better), across 10,000 simulated dinners. Same starting list, same final pick of one; only the spacing of the cuts differs.
RoundsGeometric · avgLinear · avgGeometric · minLinear · min
20.140.190.110.34
30.160.190.190.36
40.140.160.090.21
80.140.140.110.10

Average regret below the best-possible restaurant (lower is better); bold marks the better schedule in each pair. “avg” scores a pick by the two diners’ mean rating, “min” by the unhappier diner.

It comes down to how many rounds you get. With only a handful, geometric narrowing wins. On the egalitarian min measure at two rounds it isn’t close: regret of 0.11 versus 0.34, roughly three times better. The edge fades with more rounds, though. By eight passes the two schedules are neck and neck on both measures.

When the schedule matters

The gap is almost entirely in the min measure, and I think the reason is that a good min pick is a conjunction: you need a restaurant that both people rate highly, which is rarer and easier to wreck than a place that merely scores well on average. Geometric narrowing keeps the shortlist wide through the early turns, so both diners get to meaningfully shape it before it collapses to one. With only a couple of linear rounds the trimming is lopsided: whoever moves last effectively dominates, and the other person gets stuck with something they only tolerate. The average measure forgives a pick like that; the min doesn’t.

Give the process enough rounds and the alternation rebalances on its own, which is why everything converges by round eight. So the schedule only matters while rounds are scarce, which at dinner they always are.

Run it yourself

The whole experiment is a handful of lines: generate ratings, alternate top-k culls down a chosen schedule, and compare the survivor to the best-possible pick.

import numpy as np

# Each restaurant gets two independent ratings (one per diner), ~N(5, 1).
def generate(n):
    return np.random.normal(5, 1, (2, n))

# One turn: the active diner keeps their top `keep`; the rest are cut.
def cull(r, diner, keep):
    order = np.flip(np.argsort(r[diner]))   # that diner's ranking, best first
    return r[:, order][:, :keep]

def run(r, counts):              # counts[i] = how many survive round i
    for i, keep in enumerate(counts):
        r = cull(r, i % 2, keep)  # the two diners alternate turns
    return r[:, 0]                # the surviving pick (its two ratings)

# Same start (30), end (1), and rounds — only the cut SPACING differs:
n, rounds = 30, 8
geometric = np.flip([int(n**p) for p in np.linspace(0, 1, rounds, endpoint=False)])
#   -> [19, 12, 8, 5, 3, 2, 1, 1]   constant FRACTION removed each round
linear    = np.flip(np.linspace(1, n, rounds).astype(int))
#   -> [30, 25, 21, 17, 13, 9, 5, 1]   constant COUNT removed each round

# regret = (best restaurant for that measure) - (run's pick), over 10k trials.

In practice

Honestly, none of this helps you much. Restaurant ratings aren’t a bell curve, there’s a cutoff below which nobody bothers going and a long tail of a few great spots, so the tidy ~N(5,1) assumption is wrong from the start. And real people don’t communicate by silently alternating vetoes; you talk, you say what you actually want, you overrule the model in one sentence. The one thing I’d carry to an actual dinner is mild: when you only get a couple of passes, cut by fractions rather than fixed numbers, so the list stays wide while both of you still have a say. Otherwise it was a fun way to spend a bored afternoon.

Kept simple on purpose: independent normal ratings, greedy top-k turns, no bluffing or strategy between the diners. I wouldn’t read the exact numbers as gospel, but the shape of the result, geometric beating linear when rounds are scarce and most of all for the unhappier diner, held up across every setting I tried.