Schelling's model for happiness: Recall that in a 1D line of stars and zeros (to use Schelling's terminology), an element is "happy" if at least half of its neighbors (defined as the four elements to the left and four elements to the right) are like it, and "unhappy" otherwise. For those near the end of the line the rule is that, of the four neighbors on the side toward the center plus the one, two or three outboard neighbors, at least half must be like oneself.
Your assignment is to implement the Schelling model using zeros and ones to indicate the two types of elements. As with Schelling's original paper, you will play twice through the entire list, moving each element in a row (possibly moving a given element multiple times if it goes to the right). Print out the state of the list at each step so that you can see how the solution evolves.
Break up your code into functions whenever possible, and add comments to make sure that we know what's going on!
In [ ]:
import random
import math
In [ ]:
def initialize_list(array_size=32, randseed=8675309):
'''
This function optionally takes in an array size and random seed
and returns the initial neighborhood that we're going to start
from - a string of zeros and ones. If no arguments are given, it
defaults to the values specified.
'''
random.seed(randseed)
initial_list = []
for i in range(array_size):
initial_list.append(random.randint(0,1))
return initial_list
In [ ]:
def is_happy(my_list, my_value, my_index):
'''
This function assumes that my_list has a value (my_value)
popped out of it already, and checkes to see if my_value
would be happy in my_list at index my_index. It returns
'True' if happy and 'False' if unhappy under those circumstances.
'''
# do some error-checking (is the index within the allowed range?)
if my_index < 0 or my_index > len(my_list):
print("you've made an indexing error!", my_index)
start = my_index-4 # start 4 to the left
end = my_index+4 # end 3 to the right b/c we count the value at my_index too
# if the starting value is out of bounds, fix it
if start < 0:
start = 0
# if the ending value is out of bounds, fix it. note that we want to go to
# len(list), not len(list)-1, because range() goes to 1 before the end of
# the range!
if end > len(my_list):
end = len(my_list)
# keep track of the neighbors that are like me
neighbors_like_me = 0
# keep track of total neighbors
total_neighbors = 0
# loop over the specified range
for i in range(start,end):
if my_list[i] == my_value: # if this neighbor is like me, keep track of that
neighbors_like_me += 1
total_neighbors+=1 # also keep track of total neighbors
# happy if at least half are like me, unhappy otherwise
# note: it's *at least* half because we're not double-counting our
# own value
if neighbors_like_me/total_neighbors >= 0.5:
return True
else:
return False
In [ ]:
def where_to_move(my_list, my_value, my_index):
'''
Given a neighborhood (my_list), a value (my_value), and the index
that it started at (my_index), figure out where to move my_value
so that it's happy. This assumes that my_value is unhappy where it
is, by the way! This function then returns the index where my_value
should move to in order to be happy.
'''
# this block of code steps to the left to see where (if anywhere) it's
# happy to the left. If it continues to be unhappy, it'll stop when it
# is about to step off the end of the list.
left_index=my_index-1
left_happy=False
while left_happy==False and left_index >= 0:
left_happy = is_happy(my_list,my_value,left_index)
if left_happy==False:
left_index -= 1
# as above, but to the right.
right_index=my_index+1
right_happy=False
while right_happy==False and right_index < len(my_list):
right_happy = is_happy(my_list,my_value,right_index)
if right_happy==False:
right_index += 1
# now we figure out where the new index should be!
if left_index < 0 and right_index < len(my_list):
# can't be happy to the left; only possible answer is right_index
new_index = right_index
elif left_index >= 0 and right_index > len(my_list):
# can't be happy to the right; only possible answer is left_index
new_index = left_index
elif left_index >= 0 and right_index <= len(my_list):
# we're within bounds, so now check to see which side is closer.
# if they're the same we move it to the left. (This was never specified
# by Schelling, so we have to make a choice on that.)
if math.fabs(left_index-my_index) > math.fabs(right_index-my_index):
new_index = right_index
else:
new_index = left_index
else:
# this should only ever be called if something goes horribly wrong.
print("something has gone wrong in where_to_move!")
return new_index;
In [ ]:
def neighborhood_print(neighborhood, note=''):
'''
This is a convenience function to take our neighborhood list,
make a string of stars and zeros out of it, and print the string
plus optional text at the end. It's not necessary but it looks pretty.
'''
neighborstring=''
for i in range(len(neighborhood)):
if(neighborhood[i]) > 0:
neighborstring += '*'
else:
neighborstring += '0'
# make sure optional text is a string
if type(note)!=str:
note = str(note)
# add an extra space to make it look nice!
if note != '':
note = ' ' + note
neighborstring += note
print(neighborstring)
In [ ]:
# initialize list to defaults
neighborhood = initialize_list(array_size=32)
neighborhood_print(neighborhood, 'initial state')
# do 2 loops over the list
for i in range(2):
this_index = 0
# step through the neighborhood once
while this_index < len(neighborhood):
this_val = neighborhood.pop(this_index)
if is_happy(neighborhood,this_val,this_index):
# if we're happy where we are, don't change anything!
neighborhood.insert(this_index,this_val)
else:
# we're unhappy; we need to figure out where to move and then move.
new_index = where_to_move(neighborhood,this_val,this_index)
neighborhood.insert(new_index,this_val)
neighborhood_print(neighborhood, this_index)
# increment this_index or we'll never stop looping
this_index += 1
# print out the final state, just to see what it's like.
neighborhood_print(neighborhood, 'final state!')
In [ ]:
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(neighborhood,'ro')
plt.xlim(-2,len(neighborhood)+1)
plt.ylim(-0.1,1.1)