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.
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/lab8/* .
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
personList
s.
More interesting, though, are the "old age" deaths.
We want to give the function a birth year and have it remove all
person
s 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.
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.
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.
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.
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.
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".
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.
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.
toArray()
Write the function toArray()
which will
convert a list to an array of person
s.
This will involve allocating an appropriate array
(use list->size
to determin how many things
should be in the array)
and populating it.
After this, all tests should pass (or not crash).
Ok, now you can try out the simulation itself.
make ./population
When your program is working, ou 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?