Héritage
Problème à résoudre
Le groupe sanguin d'une personne est déterminé par deux allèles (c'est-à-dire différentes formes d'un gène). Les trois allèles possibles sont A, B et O, dont chaque personne possède deux (éventuellement le même, éventuellement différent). Chaque parent d'un enfant transmet aléatoirement l'un de ses deux allèles de groupe sanguin à son enfant. Les combinaisons possibles de groupes sanguins sont donc : OO, OA, OB, AO, AA, AB, BO, BA et BB.
Par exemple, si un parent a le groupe sanguin AO et l'autre parent a le groupe sanguin BB, alors les groupes sanguins possibles de l'enfant seraient AB et OB, selon l'allèle reçu de chaque parent. De même, si un parent a le groupe sanguin AO et l'autre OB, alors les groupes sanguins possibles de l'enfant seraient AO, OB, AB et OO.
Dans un fichier appelé inheritance.c
dans un dossier appelé inheritance
, simulez l'héritage des groupes sanguins pour chaque membre d'une famille.
Démo
Code de distribution
Pour ce problème, vous étendez les fonctionnalités du code fourni par le personnel de CS50.
Connectez-vous sur cs50.dev, cliquez sur votre fenêtre de terminal et exécutez cd
tout seul. Vous devriez constater que l'invite de votre fenêtre de terminal ressemble à celle-ci :
$
Exécutez ensuite
wget https://cdn.cs50.net/2023/fall/psets/5/inheritance.zip
afin de télécharger un ZIP appelé inheritance.zip
dans votre espace de codes.
Ensuite, exécutez
unzip inheritance.zip
pour créer un dossier appelé inheritance
. Vous n'avez plus besoin du fichier ZIP, vous pouvez donc exécuter
rm inheritance.zip
et répondez par « y » suivi de Entrée à l'invite pour supprimer le fichier ZIP que vous avez téléchargé.
Tapez maintenant
cd inheritance
suivi de Entrée pour vous déplacer dans (c'est-à-dire ouvrir) ce répertoire. Votre invite devrait maintenant ressembler à celle-ci.
inheritance/ $
Exécutez ls
tout seul, et vous devriez voir et voir un fichier nommé inheritance.c
.
Si vous rencontrez des problèmes, suivez à nouveau ces mêmes étapes et essayez de déterminer où vous vous êtes trompé !
Détails de mise en œuvre
Terminez l'implémentation de inheritance.c
, de sorte qu'il crée une famille d'une taille de génération spécifiée et attribue des allèles de groupe sanguin à chaque membre de la famille. L'allèle le plus ancien se voit attribuer des allèles de manière aléatoire.
- La fonction
create_family
prend un entier (generations
) en entrée et doit allouer (comme viamalloc
) uneperson
pour chaque membre de la famille de ce nombre de générations, renvoyant un pointeur vers laperson
dans la plus jeune génération.- Par exemple,
create_family(3)
doit renvoyer un pointeur vers une personne ayant deux parents, chaque parent ayant également deux parents. - Chaque
person
doit se voir attribuer desallèles
. La génération la plus ancienne doit avoir des allèles choisis au hasard (comme en appelant la fonctionrandom_allele
), et les générations plus jeunes doivent hériter d'un allèle (choisi au hasard) de chaque parent. - Chaque
person
doit se voir attribuer desparents
. La génération la plus ancienne doit avoir les deuxparents
définis surNULL
, et les générations plus jeunes doivent avoir desparents
comme tableau de deux pointeurs, chacun pointant vers une structureperson
différente.
- Par exemple,
Conseils
Comprendre le code dans inheritance.c
Jetez un œil au code de distribution dans inheritance.c
.
Remarquez la définition d'un type appelé person
. Chaque personne possède un tableau de deux parents
, chacun étant un pointeur vers une autre structure person
. Chaque personne possède également un tableau de deux allèles
, chacun étant un char
(soit 'A'
, 'B'
, soit 'O'
).
// Chaque personne a deux parents et deux allèles
typedef struct person
{
struct person *parents[2];
char alleles[2];
}
person;
Maintenant, jetez un œil à la fonction main
. La fonction commence par « amorcer » (c'est-à-dire fournir une entrée initiale à) un générateur de nombres aléatoires, que nous utiliserons plus tard pour générer des allèles aléatoires.
// Initialiser le générateur de nombres aléatoires
srand(time(0));
La fonction main
appelle ensuite la fonction create_family
pour simuler la création de structures person
pour une famille de 3 générations (c'est-à-dire une personne, ses parents et ses grands-parents).
// Créer une nouvelle famille avec trois générations
person *p = create_family(GENERATIONS);
Nous appelons ensuite print_family
pour imprimer chacun de ces membres de la famille et leurs groupes sanguins.
// Imprimer l'arbre généalogique des groupes sanguins
print_family(p, 0);
Enfin, la fonction appelle free_family
pour libérer
toute mémoire précédemment allouée avec malloc
.
// Libérer la mémoire
free_family(p);
Les fonctions create_family
et free_family
sont à vous d'écrire!
Compléter la fonction create_family
La fonction create_family
doit renvoyer un pointeur vers une personne
qui a hérité son groupe sanguin du nombre de générations
donné en entrée.
- Notez d'abord que ce problème offre une bonne opportunité de récursivité.
- Pour déterminer le groupe sanguin de la personne actuelle, vous devez d'abord déterminer les groupes sanguins de ses parents.
- Pour déterminer les groupes sanguins de ces parents, vous devez d'abord déterminer les groupes sanguins de leurs parents. Et ainsi de suite jusqu'à ce que vous atteigniez la dernière génération que vous souhaitez simuler.
Pour résoudre ce problème, vous trouverez plusieurs TODO dans le code de distribution.
Tout d'abord, vous devez allouer de la mémoire pour une nouvelle personne. Rappelez-vous que vous pouvez utiliser malloc
pour allouer de la mémoire et sizeof(person)
pour obtenir le nombre d'octets à allouer.
// Allouer de la mémoire pour une nouvelle personne
person *new_person = malloc(sizeof(person));
Ensuite, vous devez vérifier s'il reste des générations à créer, c'est-à-dire si générations > 1
.
Si générations > 1
, alors il y a encore des générations qui doivent être allouées. Nous avons déjà créé deux nouveaux parents, parent0
et parent1
, en appelant récursivement create_family
. Votre fonction create_family
doit ensuite définir les pointeurs parents de la nouvelle personne que vous avez créée. Enfin, attribuez les deux allèles
à la nouvelle personne en choisissant aléatoirement un allèle de chaque parent.
- N'oubliez pas que pour accéder à une variable via un pointeur, vous pouvez utiliser la notation fléchée. Par exemple, si
p
est un pointeur vers une personne, alors un pointeur vers le premier parent de cette personne peut être accédé parp->parents[0]
. -
Vous pouvez trouver la fonction
rand()
utile pour attribuer aléatoirement des allèles. Cette fonction renvoie un entier compris entre0
etRAND_MAX
, ou32767
. En particulier, pour générer un nombre pseudo-aléatoire qui vaut soit0
soit1
, vous pouvez utiliser l'expressionrand() % 2
.// Créer deux nouveaux parents pour la personne actuelle en appelant récursivement create_family person parent0 = create_family(générations - 1); person parent1 = create_family(générations - 1);
// Définir des pointeurs parents pour la personne actuelle new_person->parents[0] = parent0; new_person->parents[1] = parent1;
// Attribuer aléatoirement les allèles de la personne actuelle en fonction des allèles de ses parents new_person->allèles[0] = parent0->allèles[rand() % 2]; new_person->allèles[1] = parent1->allèles[rand() % 2];
Supposons qu'il n'y ait plus de générations à simuler. C'est-à-dire que générations == 1
. Si c'est le cas, il n'y aura pas de données parents pour cette personne. Les deux parents de votre nouvelle personne doivent être définis sur NULL
et chaque allèle
doit être généré aléatoirement.
// Définir des pointeurs parents sur NULL
new_person->parents[0] = NULL;
new_person->parents[1] = NULL;
// Attribuer aléatoirement des allèles
new_person->allèles[0] = random_allele();
new_person->allèles[1] = random_allele();
Enfin, votre fonction doit renvoyer un pointeur vers la personne
qui a été allouée.
// Retourner une personne nouvellement créée
return new_person;
Compléter la fonction free_family
La fonction free_family
doit accepter en entrée un pointeur vers une personne
, libérer de la mémoire pour cette personne, puis libérer récursivement de la mémoire pour tous ses ancêtres.
- Puisqu'il s'agit d'une fonction récursive, vous devez d'abord gérer le cas de base. Si l'entrée de la fonction est
NULL
, alors il n'y a rien à libérer, votre fonction peut donc renvoyer immédiatement. - Sinon, vous devez
free
récursivement les deux parents de la personne avant defree
l'enfant.
Ce qui suit est un indice, mais voici comment faire !
// Libérer `p` et tous les ancêtres de `p`.
void free_family(person *p)
{
// Gérer le cas de base
if (p == NULL)
{
return;
}
// Libérer les parents récursivement
free_family(p->parents[0]);
free_family(p->parents[1]);
// Libérer l'enfant
free(p);
}
Procédure pas à pas
Vous ne savez pas comment résoudre ?
Comment tester
Lors de l'exécution de ./inheritance
, votre programme doit respecter les règles décrites dans le contexte. L'enfant doit avoir deux allèles, un de chaque parent. Les parents doivent chacun avoir deux allèles, un de chacun de leurs parents.
Par exemple, dans l'exemple ci-dessous, l'enfant de génération 0 a reçu un allèle O des deux parents de génération 1. Le premier parent a reçu un A du premier grand-parent et un O du deuxième grand-parent. De même, le deuxième parent a reçu un O et un B de ses grands-parents.
$ ./inheritance
Enfant (génération 0) : groupe sanguin OO
Parent (génération 1) : groupe sanguin AO
Grand-parent (génération 2) : groupe sanguin OA
Grand-parent (génération 2) : groupe sanguin BO
Parent (génération 1) : groupe sanguin OB
Grand-parent (génération 2) : groupe sanguin AO
Grand-parent (génération 2) : groupe sanguin BO
Justesse
check50 cs50/problems/2024/x/inheritance
Style
style50 inheritance.c
Comment soumettre
submit50 cs50/problems/2024/x/inheritance