Galton

April 10, 2012

In the previous exercise we had one of the simulations from A. K. Dewdney’s Five Easy Pieces, and today we have another. You’ve all seen a Galton board, invented by the Victorian statistician Sir Francis Galton; a series of pegs is arranged in diagonal columns at the top, a marble is dropped into the center of the top row, and it falls through the rows, moving left or right at each peg, until it drops into a bin at the bottom of the board, one bin under each gap in the final row of pegs. Each individual marble can go left or right at each peg with equal probability, but the overall effect after several marbles are dropped is to form a bell-shaped curve in the bins at the bottom.

Your task is to write a program to simulate a Galton board, showing the bell-shaped distribution in a histogram. When you are finished you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.

Advertisement

Pages: 1 2

8 Responses to “Galton”

  1. […] today’s Programming Praxis exercise, our goal is to simulate a Galton board and display the frequencies of […]

  2. My Haskell solution (see http://bonsaicode.wordpress.com/2012/04/10/programming-praxis-galton/ for a version with comments):

    import Control.Applicative
    import Control.Monad
    import System.Random
    
    marble :: Int -> IO Int
    marble bins = sum . take (bins - 1) . randomRs (0, 1) <$> newStdGen
    
    marbles :: Num a => Int -> Int -> IO [a]
    marbles n bins = flip fmap (replicateM n $ marble bins) (\results ->
        map (fromIntegral . length . flip filter results . (==)) [0..bins - 1])
    
    histogram :: RealFrac a => a -> [a] -> IO ()
    histogram w cols = mapM_ (\n -> 
        putStrLn $ replicate (ceiling $ n * w / maximum cols) '*') cols
    
    main :: IO ()
    main = histogram 20 =<< marbles 1000 8
    
  3. uberlazy said
    (defn 
      directions
      []
      (for [_ (range)]
        (rand-int 2)))
    
    (defn single-pass [size]
      (reduce + 0 (take size (directions))))
    
    (defn galton [size passes]
      (apply merge-with +
             (for [_ (range passes)]
               {(single-pass size) 1})))
    
    (defn histogram
      ([size passes] (histogram size passes 20))
      ([size passes scale-factor]
         (let [g (galton size passes)]
           (doseq [i (range (inc size))
                   :let [cnt (g i 0)]]
             (println (format "%5d %5d %s"
                              i
                              cnt
                              (apply str
                                     (take  (* scale-factor (/ cnt passes))
                                            (repeat "*")))))))))
    
  4. Jussi Piitulainen said

    In R, after realizing that the sum of 200 Bernoulli trials with p=0.5 (each success means falling to the right at a pin, incrementing the bin number by 1) is a binomial trial with n=200 and p=0.5, one obtains the final result thus.

    plot(table(rbinom(1000, 200, 0.5)))
    

    That is a bar plot for 1000 balls through 200 levels of pins on the board, hence 201 bins. The real histogram command of R has also the tabulation of values built-in, but I think the bar plot obtained above looks nice for this.

    hist(rbinom(1000, 200, 0.5))
    [/source]
    It's equally easy to obtain 200 Bernoulli values and sum them to get the bin number of one ball, but to repeat that for 1000 balls would require some sort of loop, I think.
    [sourcecode lang="css"]
    sum(rbinom(200, 1, 0.5))
    
  5. ardnew said

    Perl solution, output scaled to $MAX_WIDTH characters

    use strict;
    use warnings;
    
    my $MAX_WIDTH = 320;
    my $SYMBOL    = '*';
    
    sub drop
    {
      my ($nbins, $nballs, @bins) = @_;
      map { ++$bins[grep { int(rand(2)) } 1 .. $nbins] } 1 .. $nballs;
      return @bins;
    }
    
    die "\nusage:\n\t$0 <bins> <balls>\n" unless 
      scalar @ARGV > 1;
      
    printf("%5d: %s\n", $_, $SYMBOL x ($_ * $MAX_WIDTH / $ARGV[1])) 
      foreach (drop(@ARGV));
    
  6. Mike said

    Here’s a quick and dirty Galton board animation in Python 2.7.

    from collections import defaultdict
    from math import sin, cos, radians as rads
    from random import choice
    from turtle import *
    
    R = 5
    
    def galton(nlevels=11, trace=False, speed=6):
        setup(width=500, height=800, startx=0, starty=0)
        delay(0)
        pen(speed=10, shown=False)
        goto(-200, -350)
        clear()
        goto( 200, -350)
        pen(pendown=False)
        for n,y in enumerate(range(nlevels*14, -1, -14), 1):
            for x in range(-7*n, 7*n+1, 14):
                goto(x, y)
                dot(3)
        pen(speed=speed)
        
        angles = (rads(d) for d in range(0,361,36))
        addshape('marble', tuple((R*sin(r),R*cos(r)) for r in angles))
        shape('marble')
        
        hist = defaultdict(lambda:-343)
        while True:
            pen(shown=False, pendown=False)
            x,y = 0, nlevels*14
            goto(x, y)
            pen(shown=True, pendown=trace)
            delay(5)
            
            while y > 0:
                if y%14:
                    x += choice((-7,7))
                    goto(x, y)
                y -= 7
                goto(x, y)
    
            delay(1)
            while y > hist[x]:
                y -= 7
                goto(x, y)
                
            stamp()
            hist[x] += 7
            
            if hist[x] > 0:
                break
    
  7. My answer in Go:

    package main
    
    import (
    	"fmt"
    	"time"
    	"math/rand"
    )
    
    func galtonStep(ball, peg chan int) {
    	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
    	for {
    		pos := <- ball
    		switch coin := rng.Float32(); true {
    			case coin < 0.5:
    				peg <- pos
    			case coin >= 0.5:
    				peg <- pos + 1
    		}
    	}
    }
    
    func galtonBoard(depth int) (ball, bin chan int) {
    	c := make(chan int)
    	ball, bin = c, c
    
    	for i := 0; i < depth; i++ {
    		prev := bin
    		bin = make(chan int)
    		go galtonStep(prev, bin)
    	}
    
    	return
    }
    
    func galtonSimulate(depth, count int) (bins []int) {
    	bins = make([]int, depth + 1)
    
    	ball, bin := galtonBoard(depth)
    	for i := 0; i < count; i++ {
    		ball <- 0
    		bins[<- bin]++
    	}
    
    	return
    }
    
    func max(values []int) int {
    	r := values[0]
    	for _, v := range(values) {
    		if v > r {
    			r = v
    		}
    	}
    	return r
    }
    
    func printHist(cols int, hist []int) {
    	maxv := max(hist)
    
    	for bin, v := range(hist) {
    		count := cols * v / maxv
    		fmt.Printf("%02d: ", bin)
    		for i := 0; i < count; i++ {
    			fmt.Print("*")
    		}
    		fmt.Println()
    	}
    }
    
    func main() {
    	printHist(65, galtonSimulate(20, 1000))
    }
    
  8. David said

    Forth version, with a RNG developed in an earlier exercise (not included here.)

    include random
    
    9 constant #Bins
    25 constant HGRAM_WIDTH
    
    : int-array ( n -- )
        create 
            cells allot
        does>
            swap cells + ;
    
    #Bins int-array galton-board
    
    : init-galton  ( -- )  \ clear the marbles from the machine
        ['] galton-board >body  #bins cells  0 fill ;
    
    : .galton
        #bins 0 DO  i galton-board ?  LOOP ;
    
    : drop-marble  ( -- )   \ drop a marble into the machine
        0
        [ #bins 1- ] literal 0 DO  2 random +  LOOP
        galton-board ++ ;
    
    : max-galton  ( -- n )
        0
        #bins 0 DO  i galton-board @ max  LOOP ;
    
    : stars  ( n -- )
        0 ?DO  [char] * emit  LOOP ;
    
    : histogram  ( width -- )
        max-galton  locals| board-max column-max |
        cr ." BIN"
    
        #bins 0 DO
            cr
            i 3 .r space
            i galton-board @  column-max board-max */  stars
         LOOP ;
    
    : galton ( marbles )
        init-galton
        0 DO  drop-marble  LOOP
        HGRAM_WIDTH histogram ;
    

    Execution:

    100000 galton
    BIN
      0
      1 **
      2 *********
      3 ********************
      4 *************************
      5 ********************
      6 **********
      7 **
      8  ok
    

Leave a Reply

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 )

Facebook photo

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

Connecting to %s

%d bloggers like this: