Lab 7: A population simulation

The goal of this lab is to practice working with pointers and dynamic memory. It also reviews linked lists, putting them now in the context of C. Quite a bit of the program is written for you; you need to add some parts that have to do with structs and dynamic allocation of memory.

1. Introduction and setup

Review the pre-lab reading as necessary for the premise.

Make a directory for this project and copy the given code.

cp /homes/tvandrun/Public/cs245/lab7/* .

2. Inspecting the given code.

A. The simulation itself, population.c

The first thing to do is to become familiar with the given code. Start by looking at the application population.c.

Before the main function there are a bunch of variables declared that control the parameter like marriage, birth, an death rates. (These can be tweaked using flags.) You'll also see a collection of surnames stored in an array.

The entire population is modeled by a personList. We populate it initially with 300 people, 5 for each of the 60 available surnames. Since we are going to start the simulation at year 0, we give each of them a birth year of -19. Note that we have a function add() for personList.

The big loop represents the years. For each year we first handle births. This requires getting a list of all married females. Notice that the we have a function for finding all such persons from a personList, and it returns them in another, smaller personList. Then for each iteration of the inner loop (for births), a random female is chosen from that list; take note that there is a function for picking an element at random from a personList.

Next, the section on marriages. We use functions to find all the males and females in a personList who are eligible for getting married this year.

The section on premature deaths shows that we need a removePerson() function for personLists. More interesting, though, are the "old age" deaths. We want to give the function a birth year and have it remove all persons with birth years earlier than that. However, we also want to update the spouses of all people that have died so that they now have null spouses and will appear as single. To do this, we need the function removeAllAge() to return all the items it removed---as a separate personList. Oh, but also, we want to iterate through that list, and so we would like to turn it into an array. Hence we need a toArray() function. Also, removing a person from a list implied that the corresponding person node should be deallocated---but not necessarily that the person should be deallocated. In the death sections, we do want to deallocate the person who dies, and that is done with a destroyPerson() function, which does the deallocation.

B. Modeling people, person.h and person.c

Then look at person.h. Values of this struct ("instances") will model individuals in the population. While a surname is a String, a given name is just a number serving as a unique identifier. A person has a birth year used to determine the person's age, and a person has a reference to a spouse. Finally, a person has a personList representing the children of this person. Glance at person.c and notice there are two functions for you to write.

C. Modeling the population, personList and personNode

Look at personList.h. You'll see that the list struct itself is mainly a place holder for the head node. See also personNode.h, and make sure you understand the difference between a person and a person node.

This diagram will help you put these all together and keep them straight:

3. Completing person and personList

There is another program, test.c, which we will use to test the correctness of your changes to person.c and personList.c before running the simulation. Out of the box it will crash. As you work through this lab, more and more tests will pass.

A. makePerson()

Write the function makePerson(). This is analogous to a constructor. But first you need to allocate the person struct value you will return, using malloc. The initialize all the fields of the person struct. You can make an empty list of children using the makeList() function.

Test this by compiling the test program and running it.

make test
./test

The first test (test A) for makePerson should pass. You'll see a few others that "DIDN'T CRASH", and then test E will crash. We'll worry about those later.

B. destroyPerson()

Now write the function destroyPerson() to undo all the things you did in makePerson(). The main idea is to deallocate this person safely. You need to deallocate the list of children (use the function destroyList(); you don't need to destroy/deallocate the children in the list, since they might still be alive), set the person's pointer fields to null, and then free the person struct itself.

(Make sure you don't deallocate the surname itself. That array is in the memory space for global data. If you try to free it, you'll get a lot of blood on the screen.)

Compile and test. We don't have much of a way to test this for correctness, but at this point the B test should (still) say "DIDN'T CRASH".

C. makeList

Write the function makeList() which serves as a constructor for the list. Allocate the list struct and initialize the fields appropriately for an empty list.

Compile and test. As with destroyPerson(), there isn't much to test, but now both B and C should have "DIDN'T CRASH". You will also see later tests that fail.

D. add()

Finish the add() function. The code provided will determine whether or not the thing being added is already there. If that loop finishes without returning, then add the indicated person to the list by allocating a new node, setting that node's datum and next, and updating the list's head and size.

Compile and test. Everything should pass (or not crash) through the G tests.

E. toArray()

Write the function toArray() which will convert a list to an array of persons. This will involve allocating an appropriate array (use list->size to determine how many things should be in the array) and populating it.

After this, all tests should pass (or not crash).

4. Running the program

Ok, now you can try out the simulation itself.

make 
./population

When your program is working, nu will notice the program slowing down near the end, since the population is large.

Even with all the randomization, you will almost certainly end up with statistics at the end reading between 78% and 80% of adults being married, a population of between 12000 and 22000, and about 2.4 children for each person having children.

At the end of the simulation, we report on the size of the population for each surname and give a breakdown of the male/female split for each surname. Currently, children are given surnames in the "patriarchal" manner-- each child gets his or her father's surname. You'll notice that while most surnames that are still in use after 500 years have pretty even gender splits, a fair number of surnames will have died off.

Marilyn Vos Savant has proposed that male children should take their father's surname and female children should take their mother's (premarital) surname Apart from social or philosophical reasons for or against such a scheme, for our interest it would have the effect of reducing the number of surnames that die out. Under the patriarchal system, a surname may die out not only if it fails to reproduce at all, but also if it reproduces only females.

A downside to Vos Savant's proposal is that it could lead to some surnames becoming lopsided in terms of gender balance. If a surname produced more females than males in one generation, it would be likely to produces an even greater percentage of females in the following generation. Eventually some surnames would be exclusively or almost exclusively male, others exclusively or almost exclusively female.

The variable namingPolicy is used to indicate how surnames are passed on. 0 indicates the patriarchal way, 1 indicates Vos Savant's proposal, and 2 indicates the opposite, giving male children their mother's surname, and female children their father's surname.

Your final task is to rewrite the function chooseChildName() at the end of population.c so that picks an appropriate surname for a child with a given father, mother, and gender, based on the current naming policy. Suggestion: write a switch statement on the variable namingPolicy. The function should return some default value like Xxxxxxxx, which you will see only if you have done something wrong.

The run the program several times experimenting with these policies. The -s flag with run Vos Savant's proposal, and -as will run the third option (anti-Savant). Does the third option keep the number of dead surnames low while also preserving gender balance?

Final thing to think about: if you were writing this in Java, what could (should) you use instead of a switch statement?


Thomas VanDrunen
Last modified: Wed Oct 21 11:07:42 CDT 2015