Vererbung

Aufgabe
Die Blutgruppe eines Menschen wird durch zwei Allele (d.h. verschiedene Formen eines Gens) bestimmt. Die drei möglichen Allele sind A, B und O (im Deutschen wird statt “O” normalerweise die Zahl “0” verwendet). Jeder Mensch besitzt zwei Allele, die gleich (z.B. AA oder BB), oder verschieden (z.B. AB) sein können. Jedes Elternteil eines Kindes gibt zufällig eines seiner beiden Allele an sein Kind weiter. Welche Blutgruppe sich aufgrund der vorhandenen Allele letztlich ergibt, haben Sie im eingebetteten Video erfahren - falls Sie es sich angeschaut haben (für die Aufgabe nicht zwingend erforderlich). In dieser Aufgabe geht es jedoch weniger um die letztliche Blutgruppe (d.h. die beobachtbaren Merkmale des Blutes) als vielmehr um die Vererbung der Allele (d.h. den Genotyp). Da es drei verschiedene Allele gibt und jedes Elternteil eines seiner beiden Allele an das eigene Kind weitergibt, sind die möglichen Allelkombinationen demnach: OO, OA, OB, AO, AA, AB, BO, BA und BB.
Wenn zum Beispiel ein Elternteil die Allele AO und das andere Elternteil die Allele BB hat, dann wären die möglichen Allele des Kindes AB und OB, je nachdem, welches Allel von jedem Elternteil vererbt wurde. Hat ein Elternteil die Allele AO und das andere die Allele OB, dann wären die möglichen Allele des Kindes AO, OB, AB und OO.
In dieser Aufgabe wollen wir die Vererbung der Allele für jedes Mitglied einer Familie simulieren.
Demo
Aufgabenmaterial
Für diese Aufgabe werden Sie ein von von uns zur Verfügung gestelltes Codegerüst vervollständigen.
Aufgabenmaterial herunterladen
Öffnen Sie VS Code entsprechend Ihrem Setup.
Öffnen Sie Ihr Terminalfenster und führen Sie dann cd aus. Die Eingabeaufforderung Ihres Terminalfensters sollte wie folgt aussehen:
$Geben Sie dann
wget https://inf.zone/download/exercises/05/inheritance.zipein und führen Sie den Befehl mit der Eingabetaste aus, um eine ZIP-Datei namens inheritance.zip in den aktuellen Ordner herunterzuladen. Achten Sie darauf, dass Sie das Leerzeichen zwischen wget und der folgenden URL nicht übersehen, und auch kein anderes Zeichen!
Führen Sie jetzt
unzip inheritance.zipaus um das ZIP-Archiv in einen Ordner namens inheritance zu extrahieren. Sie brauchen die ZIP-Datei nicht mehr, also können Sie
rm inheritance.zipausführen. Antworten Sie mit “y”, gefolgt von der Eingabetaste, um die heruntergeladene ZIP-Datei zu entfernen.
Führen Sie dann
cd inheritanceaus, um in dieses Verzeichnis zu wechseln. Ihre Eingabeaufforderung sollte nun wie folgt aussehen:
inheritance/ $Wenn alles wie beschrieben funktioniert hat, sollten Sie nach dem Ausführen von
lseine Datei, inheritance.c, sehen.
Überblick über inheritance.c
Betrachten wir zunächst die Definition eines Typs namens person. Jede Person hat ein Array von zwei parents, von denen jedes ein Zeiger auf eine andere person-struct ist. Jede Person hat auch ein Array mit zwei alleles, von denen jedes ein char ist (entweder A, B oder O).
// Each person has two parents and two alleles
typedef struct person
{
struct person *parents[2];
char alleles[2];
}
person;Werfen wir nun einen Blick auf die main-Funktion. Die Funktion beginnt mit dem “Seeding” (d.h. der Bereitstellung eines Anfangsinputs) eines Zufallszahlengenerators, den wir später zur Erzeugung von Zufallsallelen verwenden werden.
// Seed random number generator
srand(time(0));Diese Funktion ändert die Folge von Pseudozufallszahlen, die beim Aufruf der Funktion rand() zurückgegeben werden.
Der Aufruf von srand(time(0)) bewirkt, dass die Funktion rand() bei jedem Programmaufruf Zahlen in einer anderen Reihenfolge ausgibt, da time, d.h. die Systemzeit, bei jedem Aufruf anders ist. Daher sollte diese Funktion ein Mal vor der ersten Verwendung von rand() aufgerufen werden. Da dies bereits im Codegerüst geschieht, brauchen Sie sich um nichts weiteres zu kümmern, d.h. Sie müssen nicht jedes Mal, bevor Sie rand() aufrufen, srand() erneut aufrufen. Einzig relevant ist noch folgende Information: Wenn die Funktion rand() im weiteren Verlauf des Codes aufgerufen wird, gibt diese pseudo-zufällige Zahlen zwischen 0 und RAND_MAX (im Falle des CS50-Setups steckt dahinter die Zahl 32767) zurück - die benötigte Bibliothek <stdlib.h> ist ebenfalls bereits im Codegerüst eingebunden.
Die Funktion rand() werden Sie später noch benötigen. rand() kommt aber auch in der bereits implementierten Funktion random_allele() vor:
// Randomly chooses a blood type allele.
char random_allele()
{
int r = rand() % 3;
if (r == 0)
{
return 'A';
}
else if (r == 1)
{
return 'B';
}
else
{
return 'O';
}
}Wieso wird hier rand() % 3 verwendet?
Die main-Funktion ruft dann die Funktion create_family auf, um die Erstellung von person-structs für eine Familie mit 3 Generationen (d.h. eine Person, ihre Eltern und ihre Großeltern) zu initiieren.
// Create a new family with three generations
person *p = create_family(GENERATIONS);Wir rufen dann print_family auf, um jedes dieser Familienmitglieder mit seinen jeweiligen Allelen auszugeben.
// Print family tree of blood types
print_family(p, 0);Schließlich wird die Funktion free_family aufgerufen, um Speicher mit free freizugeben, der zuvor mit malloc allokiert wurde.
// Free memory
free_family(p);Die Vervollständigung dieser beiden Funktionen create_family und free_family ist nun Ihre Aufgabe!
Details zur Umsetzung
Vervollständigen Sie die Implementierung von inheritance.c, so dass eine Familie mit einer bestimmten Generationsgröße entsteht und jedem Familienmitglied “ABO”-Allele zugewiesen werden. Der ältesten Generation werden die Allele zufällig zugewiesen.
- Die Funktion
create_familynimmt einenint(generations) als Eingabe und sollte (etwa mitmalloc) Platz für eineperson-structfür jedes Familienmitglied zuweisen (entsprechend der spezifizierten Generationentiefegenerations) und einen Zeiger auf diepersonin der jüngsten Generation zurückgeben.- Zum Beispiel sollte
create_family(3)einen Zeiger auf eine Person mit zwei Eltern zurückgeben, wobei jedes Elternteil auch zwei Eltern hat. - Jeder
personsolltenalleleszugewiesen werden. Die älteste Generation sollte Allele haben, die zufällig ausgewählt werden (durch den Aufruf der Funktionrandom_allele), und jüngere Generationen sollten ein Allel (zufällig ausgewählt) von jedem Elternteil erben. - Jeder
personsolltenparentszugewiesen werden. Die älteste Generation sollte beideparentsaufNULLgesetzt haben, und bei jüngeren Generationen sollteparentsein Array aus zwei Zeigern sein, die jeweils auf ein anderes Elternteil zeigen.
- Zum Beispiel sollte
Hilfestellung
Klicken Sie auf die folgenden Tipps, um einige Ratschläge zu erhalten!
Vervollständigen der create_family Funktion
Die Funktion create_family sollte einen Zeiger auf eine person zurückgeben, die ihre Allele über die angegebene Anzahl von generations geerbt hat.
- Überlegen Sie zunächst, warum dieses Problem eine gute Gelegenheit für eine Rekursion bietet.
- Um die Allele der jetzigen Person zu bestimmen, müssen Sie zunächst die Allele ihrer Eltern ermitteln.
- Um die Allele dieser Eltern zu bestimmen, müssen Sie zunächst die Allele ihrer Eltern bestimmen. Und so weiter, bis Sie die letzte Generation erreicht haben, die Sie simulieren möchten.
Um dieses Problem zu lösen, finden Sie mehrere TODOs im im Code des Aufgabenmaterials.
Zuerst sollten Sie Speicher für eine neue Person zuweisen. Erinnern Sie sich, dass Sie malloc verwenden können, um Speicher zuzuweisen, und sizeof(person), um die Anzahl der benötigten Bytes zu erhalten.
// Allocate memory for new person
person *new_person = malloc(sizeof(person));Als nächstes ist zu prüfen, ob noch Generationen zu erzeugen sind, d.h. ob generations > 1 ist.
Wenn generations > 1, dann gibt es weitere Generationen, die noch erstellt werden müssen. Wir haben in dem zur Verfügung gestellten Code bereits zwei neue Eltern, parent0 und parent1, durch rekursiven Aufruf von create_family erzeugt. Sie sollten sich in der Funktion create_family dann um das Setzen der Zeiger auf die Eltern der neu erstellten Person kümmern. Anschließend sollten Sie der neuen Person beide alleles zuweisen, indem Sie zufällig ein Allel von jedem Elternteil auswählen.
- Erinnern Sie sich, dass Sie die Pfeilnotation verwenden können, um über einen Zeiger auf eine Variable zuzugreifen. Wenn zum Beispiel
pein Zeiger auf eine Person ist, dann kann auf den Zeiger auf das erste Elternteil dieser Person mitp->parents[0]zugegriffen werden. - Für die zufällige Zuweisung von Allelen ist die Funktion
rand()nützlich (Hinweis: Der Eintrag zurand()ist im Handbuch nicht entsprechend aufbereitet; Sie können für weitere Informationen auch den leichter zugänglichen Handbucheintrag zurandom()lesen; einziger Unterschied neben dem Namen: Die Funktion gibt einenlongstatt einenintzurück). Diese Funktion gibt eine ganze Zahl zwischen0undRAND_MAX, oder32767, zurück. Um eine Pseudo-Zufallszahl zu erzeugen, die entweder0oder1ist, können Sie den Ausdruckrand() % 2verwenden.
// Create two new parents for current person by recursively calling create_family
person *parent0 = create_family(generations - 1);
person *parent1 = create_family(generations - 1);
// Set parent pointers for current person
new_person->parents[0] = parent0;
new_person->parents[1] = parent1;
// Randomly assign current person's alleles based on the alleles of their parents
new_person->alleles[0] = parent0->alleles[rand() % 2];
new_person->alleles[1] = parent1->alleles[rand() % 2];Nehmen wir an, es gibt keine weiteren Generationen mehr zu simulieren. Das heißt, generations == 1. Wenn dies der Fall ist, gibt es keine Eltern für diese Person. Beide Elternteile der neuen Person sollten auf NULL gesetzt werden, und jedes allele sollte zufällig generiert werden.
// Set parent pointers to NULL
new_person->parents[0] = NULL;
new_person->parents[1] = NULL;
// Randomly assign alleles
new_person->alleles[0] = random_allele();
new_person->alleles[1] = random_allele();Schließlich sollte Ihre Funktion einen Zeiger auf die neu erstellte person zurückgeben.
// Return newly created person
return new_person;Implementierung der free_family-Funktion
Die Funktion free_family sollte als Eingabe einen Zeiger auf eine person akzeptieren, Speicher für diese Person freigeben und dann rekursiv Speicher für alle ihre Vorfahren freigeben.
- Da es sich um eine rekursive Funktion handelt, sollten Sie zunächst den Basisfall behandeln. Ist die Funktionseingabe
NULL, so gibt es nichts freizugeben, so dass die Funktion sofortreturnen kann. - Andernfalls sollten Sie beide Elternteile der Person rekursiv mit
freefreigeben, bevor Sie das Kind freigeben.
Achtung Spoiler: Der folgende Code zeigt Ihnen, wie Sie genau das umsetzen können!
// Free `p` and all ancestors of `p`.
void free_family(person *p)
{
// Handle base case
if (p == NULL)
{
return;
}
// Free parents recursively
free_family(p->parents[0]);
free_family(p->parents[1]);
// Free child
free(p);
}Testen
Wenn Sie ./inheritance ausführen, sollte Ihr Programm die in der Aufgabenstellung beschriebenen Regeln einhalten. Das Kind sollte zwei Allele haben, eines von jedem Elternteil. Die Eltern sollten jeweils zwei Allele haben, eines von jedem ihrer Elternteile.
Im folgenden Beispiel hat das Kind der Generation 0 von beiden Eltern der Generation 1 ein O-Allel erhalten. Der erste Elternteil hat vom ersten Großelternteil ein A und vom zweiten Großelternteil ein O erhalten. Analog hat der zweite Elternteil von seinen Großeltern ein O und ein B erhalten.
$ ./inheritance
Child (Generation 0): alleles OO
Parent (Generation 1): alleles AO
Grandparent (Generation 2): alleles OA
Grandparent (Generation 2): alleles BO
Parent (Generation 1): alleles OB
Grandparent (Generation 2): alleles AO
Grandparent (Generation 2): alleles BOKorrektheit
Führen Sie in Ihrem Terminal den folgenden Befehl aus, um die Korrektheit Ihrer Arbeit zu überprüfen:
check50 -l cs50/problems/2024/x/inheritanceStyle
Führen Sie den folgenden Befehl aus, um den Stil Ihres Codes mit style50 zu analysieren:
style50 inheritance.c