BRIAN: In this lab, your task is going to be
to write a program in C that simulates the inheritance of blood type genes
within a family.
To give a little bit of science background,
your blood type is determined by alleles.
Alleles are different versions of a gene.
And there are three different alleles with regard to blood type,
called A, B, and O.
Each person has two of these alleles.
Maybe you have two A's, two B's, two O's.
Or maybe you mix and match, have an A and a B, or a B and an O,
or an A or an O, for example.
And when a parent has a child, each parent passes on one of their alleles
to their child.
So for example, if we imagine two parents,
one that has a blood type of AB and one who has a blood type of OO,
tthen their child is going to inherit one blood type of little
from each of their parents.
For example, we might randomly choose A from one parent,
and between the other parent O, and ultimately get a blood type of AO.
But we also could have gotten a blood type of BO, for example.
Which of the two alleles is passed on is just chosen at random.
In order to simulate this type of inheritance inside of our program,
we're going to need some sort of data structure
that lets us represent the connection between people and their parents,
as well as what alleles they have for their blood type.
And because there is no type built into C to do just that,
we can create a type of our own using a typedef.
So here, we have typedef struct person, where every person is going
to be represented by these two fields.
Struct person star *parents[2] means that every person is going to have
an array of 2 parents, where each of those parents is going to be a pointer
to another person.
Meanwhile, every person is also going to have an array of two characters called
alleles, representing which two alleles, A, B, or O, this particular person has.
And because each person has two alleles, this is going to be an array of size 2
to represent both of those alleles.
So in the example we saw before, we might, for this particular child,
have the letter A stored at this child's alleles 0 and 0 stored at alleles 1.
And meanwhile, parents[0] would point to one of the parents and parents[1] would
point to the other parent.
Let's see how these pieces now would come together inside
of the distribution code that we give to you for the lab.
There is a fair amount of distribution code
that we already give to you inside of inheritance.c
But let's try and distill it to just what's most important.
Here we'll notice that same typedef, that we define a person
as having pointers to two parents in this array of 2 pointers
and also having an array of two characters,
representing the two alleles that this person has for their blood type.
Next up is this relevant variable called const int GENERATIONS,
a constant integer, which is set equal to the number of 3.
And this is going to represent the number of generations of data
that we're going to simulate generating.
In this case, we're simply waiting three generations
worth of data for blood types, which means we're not just
going to simulate the single child, but also generation
2, their parents, and also generation 3, that child's grandparents as well.
We then have some prototypes for functions,
but let's skip down to the main function to really see
what it is that this program is doing.
The main function is already written for you.
Nothing you need to do here.
But we start by seeding the random number generator,
just so that we're able to generate pseudo random numbers, which
is going to be useful.
Because when we decide how genes are inherited,
we want to be able to randomly choose which gene is going
to be passed on from parent to child.
Next we call the create_family function to create a family with a specified
number of generations, returning a pointer to the most recent generation
of person in that family.
So if we create a family with three generations,
we'll end up with a pointer to one person.
But using their parent pointers, we could
access that child's parents as well as that child's grandparents,
but no further if we're only looking at a family with three generations.
The create_family function will be up to you to write.
But the print family function we will write.
After you've already created a family, we've
already written for you a function that will take that family
and print them out as a hierarchical family tree,
printing out what blood type each of the people in that family has.
Finally, you'll write the free_family function,
which will free all of the memory that you may have allocated
in the process of creating this family.
Because recall that for each of the people
that you want to represent inside of your computer's memory,
you'll likely need to allocate memory for them
by using the malloc function to allocate some memory dynamically.
But any time you allocate memory with malloc,
you'll also want to free that memory, giving it back to the computer
so that it can be used for other purposes.
Here then, in the create_family function,
is going to be what you'll implement, allocating memory for a new person
and then dealing with two possible cases.
One, if there are potential parents that this child has
that we need to recursively generate, or else,
if we're just generating a single generation,
generating just a single person without any parent data.
You'll also, then, write the free_family function, which
takes as input a pointer to a person p.
And the task for this function is to free p as well as any ancestors of p.
So if person p has parents or grandparents or other ancestors
as well, you'll want to make sure to free them, too.
The print family function we've written for you.
No need to do anything here.
It takes a person and goes ahead and prints out their entire family
tree, along with their blood types.
And this random allele function that we wrote might be helpful for you, too.
It randomly chooses an allele, A, B, or O,
which can be useful if you get to a point
where you need to randomly generate an allele
to store inside of that allele's array.
So with that distribution code in mind, let's then
walk through what it is that your program is going to do.
The first thing you should do is complete the create_family function
to create a family with a specified number of generations.
In order to do so, the first thing you should do
is allocate memory for a new person, calling malloc,
passing in size of person to make sure you
have enough memory for a new person.
And then you'll use that person to potentially generate ancestors
if there are more generations that you need to work with.
So if generations is greater than 1, meaning there are more generations
that you need to generate, then you'll recursively
create those previous generations.
What does it mean to recursively create previous generations?
Well, if you're trying to create a child that has two generations of parents
then you'll recursively call the create_family function
for each of those parents where each one of those parents
will have one generation of parents, and so on and so forth.
So if generations is greater than 1, you'll
want to recursively create those previous generations.
And using the return value you get from those create_family functions,
you'll update this new person's parents, setting parents[0] equal to the result
of one recursive call and setting parents[1] equal to the result of some
other recursive call.
After you set that person's parents, recall
that every person not only has two pointers to parents,
but also has two alleles.
So now you'll need to do the actual inheritance step, inheriting
one allele from each parent at random.
So you want to inherit the alleles from the parents by looking at one parents
two alleles, randomly choosing one, and setting that as the child's allele 0.
And then doing the same with the other parent, looking at their two alleles,
randomly choosing one, and using one of those as alleles[1] so that the child
ends up with two alleles, one inherited from either parent.
Otherwise, if generations is equal to 1, that
means you're only generating one generation worth of family, that is,
an individual person with no parents.
And if that's the case, then both of the parent pointers should be set to null.
And because there is nothing to inherit, because we
don't have any data about the parents, you'll
go ahead and randomly generate alleles for each of the two
alleles for this particular person.
And recall that, in the distribution code,
we give to you a function that will randomly generate those alleles.
The final step for this lab is going to be to write the free_family function
to free all of that allocated memory.
Recall that every time you called the create_family function,
you were calling malloc at least once in order
to generate data for a new person, to allocate memory
for that person dynamically inside of the computer's heap.
Now, after you've malloced that memory, it's
your task at the end of the program to free all of that memory
up and give it back to the computer.
In order to do so, you'll want to first handle the base case.
If the input to free_family is null, there is no action you should take.
You should never be freeing the null pointer.
But after that, you'll want to deal with the recursive case,
recursively freeing the parents, and then after that, freeing the child.
How do you recursively free the parents?
Well, you'll want to call the free_family function
on each of the child's two parents.
Recursively then, that will also call for a family
on the child's grandparents and higher generations,
if there are other generations, as well.
And after those parents have been freed then you can use free in order
to free the child as well, ultimately freeing all of the memory
that you've allocated throughout the program.
After you've written these two functions,
you should then be able to run your inheritance program in order
to simulate the inheritance of blood types across multiple generations,
seeing what blood types the grandparents had,
seeing how those blood types translated into the blood types of a child's
parent, and ultimately, what blood type that child inherited as well.
My name is Brian.
And this was inheritance.