Voters
April 6, 2012
It’s election year in the United States, and in today’s exercise we have an election simulation. It comes to us from chapter “Five Easy Pieces” in A. K. Dewdney’s book The Armchair Universe, which is a compilation of some of Dewdney’s “Computer Recreations” columns in Scientific American. Dewdney calls his simulation voters.
The simulation is simple. A rectangular x-by-y grid contains cells that represent voters. Each cell has eight horizontally, vertically or diagonally adjacent neighbors, with the grid wrapping around on all sides to form a torus. Each cell can contain one of two values, which we call 0 and 1, not necessarily in order, instead of Democrat or Republican. At each time step, one cell, chosen at random, changes its allegiance to that of one of its eight neighbors, chosen at random; in some cases that random choice leaves the allegiance unchanged.
The current state of the simulation is displayed on the screen, with easily distinguishable symbols for 0 and 1. The simulation continues until it reaches a steady state — totalitarianism — in which all cells have the same value. It is mesmerizing to watch the screen as voting blocks form and changing allegiances sweep across the grid.
Your task is to write a program that displays the simulation described above. 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.
here’s my python version; much the same idea.
voters
Python 2.7 version with graph display using the built-in turtle graphics module.
from random import choice, randint from turtle import * NROWS = 10 HNROWS = NROWS/2 NCOLS = 10 HNCOLS = NCOLS/2 DOTSIZE = 20 COLOR = ('red', 'blue') def sim(): reset() hideturtle() penup() speed(0) delay(0) voter = [[choice(COLOR) for c in range(NCOLS)] for r in range(NROWS)] for r in range(NROWS): for c in range(NCOLS): x = (c - HNCOLS)*DOTSIZE y = (r - HNROWS)*DOTSIZE goto(x,y) dot(20, voter[r][c]) n = sum(row.count(COLOR[0]) for row in voter) while 0 < n < NROWS*NCOLS: c, r = randint(0, NCOLS-1), randint(0, NROWS-1) dc,dr = choice(((-1, 1), ( 0, 1), ( 1, 1), (-1, 0), ( 1, 0), (-1,-1), ( 0,-1), ( 1,-1))) nc,nr = (c + dc)%NCOLS, (r + dr)%NROWS if voter[r][c] != voter[nr][nc]: voter[r][c] = voter[nr][nc] goto((c - HNCOLS)*DOTSIZE, (r - HNROWS)*DOTSIZE) dot(20, voter[r][c]) n += 1 if voter[nr][nc] == COLOR[0] else -1 print "{} wins!".format(COLOR[n == 0]) if __name__ == "__main__": sim()if i could vote up comments, +1 to Mike for turtle graphics
Here’s a perl implementation, with comments!
use strict; use warnings; my $MAX_ROWS = 50; my $MAX_COLS = 50; # # generates a grid whose dimensions are randomly selected # from 2 to MAX_ROWS and 2 to MAX_COLS. each component is # randomly selected to be 0 or 1. # sub rand_grid { my ($c, $r, @g) = (int(rand((shift) - 2) + 2), int(rand((shift) - 2) + 2)); push @g, [map {int(rand(2))} (0 .. $r)] for (0 .. $c); return @g; } # # prints the grid, one row per line # sub print_grid { print +(join '', @$_).$/ foreach @_; } # # randomly selects one of the 8 neighbors of a given (x,y) # coordinate # sub rand_neighbor { my ($x, $y, $n, $m) = @_; my @c = ([-1,-1],[-1, 0],[-1, 1],[ 0,-1], [ 0, 1],[ 1,-1],[ 1, 0],[ 1, 1]); my @d = @{$c[int(rand(@c))]}; return (($x + shift @d) % $m, ($y + shift @d) % $n); } # # checks if the population has reached a totalitarian state # sub totalitarian { my $s = ""; $s .= join '', @$_ foreach @_; return $s eq "1" x length $s || $s eq "0" x length $s; } # # Main routine # sub main { # generate the random grid my @grid = rand_grid(@_); # determine our grid's dimensions my ($m, $n) = (0+@grid, 0+@{$grid[0]}); # continue until we reach a steady state while (not totalitarian(@grid)) { # randomly pick a cell my ($y, $x) = (int(rand($m)), int(rand($n))); # randomly pick one of its neighbors my ($j, $k) = rand_neighbor($x, $y, $n, $m); # change our cell's allegience $grid[$y][$x] = $grid[$j][$k]; # print our current state print_grid(@grid); print +('-' x $n).$/; } } main($MAX_ROWS, $MAX_COLS);[…] It’s called “Voters” and the full description of the problem can be found here. I wrote a terminal output version and then deleted that when I started working on a GTK3+Cairo GUI […]
[…] An exercise described here. […]
Written for Ruby 1.9.3, using Curses, run it in a terminal, and give it two arguments, the width and height in grid units of the grid:
require "curses" include Curses class Simulation def initialize x, y @x = x @y = y @grid = [] @y.times { @grid << [] } populate_random() end def populate_random @y.times do |j| @x.times do |i| @grid[j][i] = rand(2) end end end def display @grid.map { |row| row.map { |x| x == 1 ? "*" : " " }.join("") }.join("\n") end def complete? flat = @grid.flatten (flat - [0] == []) or (flat - [1] == []) end def random_neighbour x, y ran_x = (x + rand(3) - 1) % @x ran_y = (y + rand(3) - 1) % @y @grid[ran_y][ran_x] end def tick coord_x = rand(@x) coord_y = rand(@y) @grid[coord_y][coord_x] = random_neighbour( coord_x, coord_y ) end end s = Simulation.new(ARGV[0].to_i, ARGV[1].to_i) init_screen() until s.complete? s.tick setpos(0,0) addstr( s.display ) refresh() endIn JavaScript:
http://jsbin.com/azebew/edit#preview
Python implementation: