rec.puzzles Archive

Compute the expected waiting time for a sequence of coin flips, or the probabilty that one sequence of coin...

probability/flips/waiting.time Added material

Problem

Compute the expected waiting time for a sequence of coin flips, or the
probabilty that one sequence of coin flips will occur before another.

Show solution
Here's a C program I had lying around that is relevant to the
current discussion of coin-flipping.  The algorithm is N^3 (for N flips)
but it could certainly be improved.  Compile with 

        cc -o flip flip.c -lm

-- Guy

_________________ Cut here ___________________

#include <stdio.h>
#include <math.h>

char *progname;              /* Program name */

#define NOT(c) (('H' + 'T') - (c))


/* flip.c -- a program to compute the expected waiting time for a sequence
             of coin flips, or the probabilty that one sequence
             of coin flips will occur before another.

    Guy Jacobson, 11/1/90
*/

main (ac, av) int ac; char **av;
{
    char *f1, *f2, *parseflips ();
    double compute ();

    progname = av[0];

    if (ac == 2) {
        f1 = parseflips (av[1]);
        printf ("Expected number of flips until %s = %.1f\n",
                f1, compute (f1, NULL));
    }
    else if (ac == 3) {

        f1 = parseflips (av[1]);
        f2 = parseflips (av[2]);

        if (strcmp (f1, f2) == 0) {
            printf ("Can't use the same flip sequence.\n");
            exit (1);
        }
        printf ("Probability of flipping %s before %s = %.1f%%\n",
                av[1], av[2], compute (f1, f2) * 100.0);
    }
    else
      usage ();
}

char *parseflips (s) char *s;
{
    char *f = s;

    while (*s)
      if (*s == 'H' || *s == 'h')
        *s++ = 'H';
      else if (*s == 'T' || *s == 't')
        *s++ = 'T';
      else
        usage ();

    return f;
}

usage ()
{
    printf ("usage: %s {HT}^n\n", progname);
    printf ("\tto get the expected waiting time, or\n");
    printf ("usage: %s s1 s2\n\t(where s1, s2 in {HT}^n for some fixed n)\n",
            progname);
    printf ("\tto get the probability that s1 will occur before s2\n");
    exit (1);
}

/*
  compute --  if f2 is non-null, compute the probability that flip
              sequence f1 will occur before f2.  With null f2, compute
              the expected waiting time until f1 is flipped

  technique:

    Build a DFA to recognize (H+T)*f1 [or (H+T)*(f1+f2) when f2
    is non-null].  Randomly flipping coins is a Markov process on the
    graph of this DFA.  We can solve for the probability that f1 precedes
    f2 or the expected waiting time for f1 by setting up a linear system
    of equations relating the values of these unknowns starting from each
    state of the DFA.  Solve this linear system by Gaussian Elimination.
*/

typedef struct state {
    char *s;                /* pointer to substring string matched */
    int len;                /* length of substring matched */
    int backup;             /* number of one of the two next states */
} state;

double compute (f1, f2) char *f1, *f2;
{
    double solvex0 ();
    int i, j, n1, n;

    state *dfa;
    int nstates;

    char *malloc ();

    n = n1 = strlen (f1);
    if (f2)
        n += strlen (f2); /* n + 1 states in the DFA */

    dfa = (state *) malloc ((unsigned) ((n + 1) * sizeof (state)));

    if (!dfa) {
        printf ("Ouch, out of memory!\n");
        exit (1);
    }

    /* set up the backbone of the DFA */

    for (i = 0; i <= n; i++) {
        dfa[i].s = (i <= n1) ? f1 : f2;
        dfa[i].len = (i <= n1) ? i : i - n1;
    }
 
    /* for i not a final state, one next state of i is simply
       i+1 (this corresponds to another matching character of dfs[i].s
       The other next state (the backup state) is now computed.
       It is the state whose substring matches the longest suffix
       with the last character changed */      
       
    for (i = 0; i <= n; i++) {
        dfa[i].backup = 0;
        for (j = 1; j <= n; j++)
          if ((dfa[j].len > dfa[dfa[i].backup].len)
              && dfa[i].s[dfa[i].len] == NOT (dfa[j].s[dfa[j].len - 1])
              && strncmp (dfa[j].s, dfa[i].s + dfa[i].len - dfa[j].len + 1,
                          dfa[j].len - 1) == 0)
            dfa[i].backup = j;
    }

    /* our dfa has n + 1 states, so build a system n + 1 equations
       in n + 1 unknowns */

    eqsystem (n + 1);

    for (i = 0; i < n; i++)
      if (i == n1)
        equation (1.0, n1, 0.0, 0, 0.0, 0, -1.0);
      else
        equation (1.0, i, -0.5, i + 1, -0.5, dfa[i].backup, f2 ? 0.0 : -1.0);
    equation (1.0, n, 0.0, 0, 0.0, 0, 0.0);

    free (dfa);

    return solvex0 ();
}


/* a simple gaussian elimination equation solver */

double *m, **M;
int rank;
int neq = 0;

/* create an n by n system of linear equations.  allocate space
   for the matrix m, filled with zeroes and the dope vector M */

eqsystem (n) int n;
{
    char *calloc ();
    int i;

    m = (double *) calloc (n * (n + 1), sizeof (double));
    M = (double **) calloc (n, sizeof (double *));

    if (!m || !M) {
        printf ("Ouch, out of memory!\n");
        exit (1);
    }

    for (i = 0; i < n; i++)
      M[i] = &m[i * (n + 1)];
    rank = n;
    neq = 0;
}

/* add a new equation a * x_na + b * x_nb + c * x_nc + d = 0.0
   (note that na, nb, and nc are not necessarily all distinct.) */

equation (a, na, b, nb, c, nc, d) double a, b, c, d; int na, nb, nc;
{
    double *eq = M[neq++]; /* each row is an equation */
    eq[na + 1] += a;
    eq[nb + 1] += b;
    eq[nc + 1] += c;
    eq[0] = d;             /* column zero holds the constant term */
}

/* solve for the value of variable x_0.  This will go nuts if
   therer are errors (for example, if m is singular) */

double solvex0 ()
{
    register i, j, jmax, k;
    register double  max, val;
    register double *maxrow, *row;


    for (i = rank; i > 0; --i) {      /* for each variable */

        /* find pivot element--largest value in ith column*/
        max = 0.0;
        for (j = 0; j < i; j++)
            if (fabs (M[j][i]) > fabs (max)) {
                max = M[j][i];
                jmax = j;
            }
        /* swap pivot row with last row using dope vectors */
        maxrow = M[jmax];
        M[jmax] = M[i - 1];
        M[i - 1] = maxrow;

        /* normalize pivot row */
        max = 1.0 / max;
        for (k = 0; k <= i; k++)
          maxrow[k] *= max;

        /* now eliminate variable i by subtracting multiples of pivot row */
        for (j = 0; j < i - 1; j++) {
            row = M[j];
            if (val = row[i])              /* if variable i is in this eq */
                for (k = 0; k <= i; k++)
                  row[k] -= maxrow[k] * val;
        }
    }

    /* the value of x0 is now in constant column of first row
       we only need x0, so no need to back-substitute */

    val = -M[0][0];

    free (M);
    free (m);

    return val;
}

_________________________________________________________________
Guy Jacobson   (201) 582-6558              AT&T Bell Laboratories
        uucp:  {att,ucbvax}!ulysses!guy           600 Mountain Avenue
    internet:  guy@ulysses.att.com         Murray Hill NJ, 07974


Additional material

[ Editor: Conway's algorithm for the odds that one sequence appears
before another, from a 1998 thread "coin flipper & independent sets". ]

Nick Wedd <Nick@maproom.co.uk> said:
> I propose to Jamie that we play the following game.  We toss a fair coin
> until either
> THHHT comes up, when Jamie wins and I pay him a dollar;  or
> HTHHH comes up, when I win and Jamie pays me a dollar.
> These two are equally probable, so the game is, I allege, fair.
>
> ObPuzzle:  what is the value to me of playing this game once?

In "Time Travel and Other Mathematical Bewilderments", Martin Gardner
describes a procedure for solving this problem, attributed to J. H.
Conway.  He says "I have no idea why it works.  It just cranks out the
answer as if by magic, like so many of Conway's other algorithms."

Let A = THHHT and B = HTHHH.  Write the four sets of pairs of A and B, one
line above the other.  Write a binary digit on top of each of the pairs,
where putting a 1 in the n'th place means "The last n digits of the top
row are the same as the first n digits of the bottom row."  So, it looks
like this:

   10001
A: THHHT
A: THHHT

   00010
A: THHHT
B: HTHHH

   01000
B: HTHHH
A: THHHT

   10001
B: HTHHH
B: HTHHH

Take each number as an n-digit binary number.  So AA=17, AB=2, BA=8, and
BB=17.  The odds that B will come up before A is AA-AB:BB-BA = 15:9 = 5:3.
(This corresponds to the 5/8 probability that Glenn came up with
independently.)

ObPuzzle: How would the mathematics change if you had more than two people
with winning combinations?  How would they change if the two strings were
of different lengths?

-- Matthew Daly <mwdaly@pobox.com>

[ Editor: the n'th place is counted from the right, as the four
examples show.  Glenn's posting (probably Glenn Rhoads's) is not in the
collected material.  With B (Wedd's sequence) first with probability
5/8, the game is worth 5/8 - 3/8 = $0.25 to Wedd per play. ]