web-dev-qa-db-fra.com

Cycles dans le logiciel d'arbre généalogique

Je suis le développeur d'un logiciel d'arbre généalogique (écrit en C++ et Qt). Je n'ai rencontré aucun problème jusqu'à ce qu'un de mes clients m'envoie un rapport de bogue par la poste. Le problème est que le client a deux enfants avec leur propre fille et qu’il ne peut donc pas utiliser mon logiciel à cause d’erreurs.

Ces erreurs sont le résultat de mes diverses affirmations et invariants sur le graphe de famille en cours de traitement (par exemple, après avoir parcouru un cycle, le programme indique que X ne peut pas être à la fois père et grand-père de Y).

Comment puis-je résoudre ces erreurs sans supprimer toutes les assertions de données?

1594
Partick Höse

Il semble que vous (et/ou votre entreprise) ayez un malentendu fondamental sur ce que devrait être un arbre généalogique.

Permettez-moi de préciser. Je travaille également pour une société qui a (parmi ses produits) un arbre généalogique dans son portefeuille et nous nous débattons avec des problèmes similaires.

Le problème, dans notre cas, et je suppose que votre cas aussi, vient du format GEDCOM qui est extrêmement prévenu de ce que devrait être une famille. Cependant, ce format contient des idées fausses sur l’aspect réel d’un arbre généalogique.

GEDCOM a de nombreux problèmes, tels que l'incompatibilité avec les relations entre personnes du même sexe, l'inceste, etc.

Nous avons modelé notre arbre généalogique sur ce qui se passe dans le monde réel: Événements (par exemple, naissances, mariages, fiançailles, unions, décès, adoptions, etc.). Nous n'imposons aucune restriction à ceux-ci, sauf pour des raisons logiquement impossibles (par exemple, on ne peut pas être son propre parent, les relations ont besoin de deux personnes, etc.)

L'absence de validation nous donne une solution plus "réelle", plus simple et plus flexible.

En ce qui concerne ce cas particulier, je suggérerais de supprimer les affirmations car elles ne sont pas universelles.

Pour afficher les problèmes (cela va se produire), je suggérerais de dessiner le même nœud autant de fois que nécessaire, en suggérant la duplication en allumant toutes les copies lors de la sélection de l’un d’eux.

727
Bert Goethals

Détendez vos affirmations.

Pas en modifiant les règles, qui sont probablement très utiles à 99,9% de vos clients pour détecter les erreurs lors de la saisie de leurs données.

Au lieu de cela, remplacez-le par une erreur "ne peut pas ajouter de relation" à un avertissement avec un "add anyway".

564
Ben Voigt

Voici le problème avec les arbres généalogiques: ce ne sont pas des arbres. Ce sont des graphes acycliques ou DAG dirigés. Si je comprends bien les principes de la biologie de la reproduction humaine, il n'y aura pas de cycles.

Autant que je sache, même les chrétiens acceptent les mariages (et donc les enfants) entre cousins, ce qui transformera l’arbre généalogique en une famille DAG.

La morale de l'histoire est la suivante: choisissez les bonnes structures de données.

224
exDM69

Je suppose que vous avez une valeur qui identifie de manière unique une personne sur laquelle vous pouvez baser vos chèques.

Ceci est un problème. En supposant que vous souhaitiez conserver la structure sous forme d'arbre, je suggère ceci:

Supposons ceci: A a des enfants avec sa propre fille.

A s'ajoute au programme sous le nom A et B. Une fois dans le rôle du père, appelons-le petit ami.

Ajoutez une fonction is_same_for_out() qui indique à la partie génératrice de sortie de votre programme que tous les liens allant à B en interne doivent aller à A lors de la présentation des données.

Cela nécessitera un travail supplémentaire pour l'utilisateur, mais je suppose que l'informatique serait relativement facile à mettre en œuvre et à gérer.

En partant de là, vous pouvez travailler sur la synchronisation de code A et B pour éviter les incohérences.

Cette solution n'est sûrement pas parfaite, mais constitue une première approche.

115
Eduard Thamm

Vous devriez vous concentrer sur ce qui fait vraiment la valeur de votre logiciel. Le temps consacré à le faire fonctionner pour UN consommateur vaut-il le prix de la licence? Probablement pas.

Je vous conseille de présenter vos excuses à ce client, de lui dire que sa situation est hors de portée pour votre logiciel et de lui rembourser.

84
christopheml

Vous devriez avoir configuré la famille Atreides (soit moderne, Dune , ou ancienne, Oedipus Rex ) en tant que test. Vous ne trouvez pas de bugs en utilisant des données assainies en tant que scénario de test.

79
user779752

C'est l'une des raisons pour lesquelles des langues comme "Go" n'ont pas d'affirmations. Ils sont habitués à traiter des cas auxquels vous n'avez probablement pas pensé trop souvent. Vous ne devez affirmer que l'impossible, pas simplement l'improbable. Faire ce dernier est ce qui donne une mauvaise réputation aux assertions. Chaque fois que vous tapez assert(, éloignez-vous pendant dix minutes et réfléchissez vraiment .

Dans votre cas particulièrement troublant, il est à la fois concevable et effrayant qu’une telle affirmation soit fausse dans des circonstances rares mais possibles. Par conséquent, manipulez-le dans votre application, ne serait-ce que pour dire "Ce logiciel n'a pas été conçu pour gérer le scénario que vous avez présenté".

Affirmer que votre arrière-arrière-arrière-grand-père est votre père impossible est une chose raisonnable à faire.

Si je travaillais pour une société de test qui a été embauchée pour tester votre logiciel, j'aurais bien sûr présenté ce scénario. Pourquoi? Chaque "utilisateur" juvénile mais intelligent va faire exactement la même chose et afficher le "rapport de bogue" qui en résulte.

59
Tim Post

Je déteste faire des commentaires sur une situation aussi délicate, mais le moyen le plus simple de ne pas réorganiser tous vos invariants est de créer un sommet fantôme dans votre graphique qui agit comme un proxy vers le père incestueux.

41
Sean

J'ai donc travaillé sur le logiciel de l'arbre généalogique. Je pense que le problème que vous essayez de résoudre est qu’il vous faut pouvoir marcher dans l’arbre sans bouger à l’infini. En d’autres termes, l’arbre doit être acyclique.

Cependant, vous semblez affirmer qu'il n'y a qu'un seul chemin entre une personne et l'un de leurs ancêtres. Cela garantira qu'il n'y a pas de cycles, mais c'est trop strict. Biologiquement, la descendance est un graphe acyclique dirigé (DAG). Le cas que vous avez est certainement un cas dégénéré, mais ce genre de chose se produit tout le temps sur des arbres plus grands.

Par exemple, si vous regardez les 2 ^ n ancêtres que vous avez à la génération n, s'il n'y avait pas de chevauchement, vous auriez plus d'ancêtres en 1000 après JC que de personnes vivantes. Donc, il doit y avoir un chevauchement.

Cependant, vous avez également tendance à avoir des cycles non valides, juste des données erronées. Si vous parcourez l'arborescence, vous devez gérer les cycles. Vous pouvez le faire dans chaque algorithme ou en charge. Je l'ai fait en charge.

Trouver de vrais cycles dans un arbre peut être fait de plusieurs manières. La mauvaise façon est de marquer chaque ancêtre d'un individu donné, et lors de la traversée, si la personne à qui vous allez passer au suivant est déjà marquée, alors coupez le lien. Cela rompra les relations potentiellement précises. La bonne façon de le faire est de commencer par chaque individu et de marquer chaque ancêtre avec le chemin d'accès à cet individu. Si le nouveau chemin contient le chemin actuel en tant que sous-chemin, alors c'est un cycle et il devrait être interrompu. Vous pouvez stocker les chemins en tant que vecteur <bool> (MFMF, MFFFMF, etc.), ce qui rend la comparaison et le stockage très rapides.

Il existe quelques autres moyens de détecter les cycles, par exemple envoyer deux itérateurs et voir s'ils se heurtent jamais au test du sous-ensemble, mais j'ai fini par utiliser la méthode de stockage local.

Notez également que vous n'avez pas besoin de couper le lien, vous pouvez simplement le changer d'un lien normal à un lien "faible", non suivi par certains de vos algorithmes. Vous voudrez également prendre soin de choisir le lien à marquer comme faible; Parfois, vous pouvez déterminer où le cycle devrait être brisé en consultant les informations sur la date de naissance, mais souvent, vous ne pouvez rien comprendre car il manque tant de données.

37
tfinniga

Une autre réponse sérieuse factice à une question idiote:

La vraie réponse est d'utiliser une structure de données appropriée. La généalogie humaine ne peut pas être entièrement exprimée en utilisant un arbre pur sans cycles. Vous devriez utiliser une sorte de graphique. En outre, parlez à un anthropologue avant d’aller plus loin dans ce processus, car il existe de nombreux autres endroits où des erreurs similaires pourraient être commises en essayant de modéliser la généalogie, même dans le cas le plus simple du "mariage monogame patriarcal occidental".

Même si nous voulons ignorer localement les relations taboues, comme indiqué ici, il existe de nombreuses manières parfaitement légales et totalement inattendues d'introduire des cycles dans un arbre généalogique.

Par exemple: http://en.wikipedia.org/wiki/Cousin_marriage

Fondamentalement, le mariage des cousins ​​n’est pas seulement commun et prévu, c’est la raison pour laquelle les humains sont passés de milliers de petits groupes familiaux à une population mondiale de 6 milliards. Cela ne peut pas fonctionner autrement.

Il y a vraiment très peu d'universels en matière de généalogie, de famille et de lignage. Presque n'importe quelle hypothèse stricte sur les normes suggérant qui peut être une tante, ou qui peut épouser qui, ou comment les enfants sont légitimés aux fins de l'héritage, peut être contrariée par une exception quelque part dans le monde ou dans l'histoire.

36
clvrmnky

Mis à part les implications juridiques potentielles, il semble bien que vous deviez traiter un "nœud" sur un arbre généalogique comme une personne prédécesseur plutôt que de supposer que le nœud peut être une personne unique.

Si le nœud d’arbre inclut une personne ainsi que les successeurs, vous pouvez créer un autre nœud plus en profondeur dans l’arborescence qui inclut la même personne avec différents successeurs.

20
Will A

Quelques réponses ont montré des moyens de conserver les assertions/invariants, mais cela semble être un mauvais usage des assertions/invariants. Les assertions permettent de s’assurer que quelque chose qui devrait être vrai est vrai, et les invariants de s’assurer que quelque chose qui ne devrait pas changer ne change pas.

Ce que vous affirmez ici, c'est que les relations incestueuses n'existent pas. Clairement, ils do existent, votre assertion est donc invalide. Vous pouvez contourner cette assertion, mais le vrai bogue réside dans l'assertion elle-même. L'assertion doit être supprimée.

13
kerkeslager

Votre arbre généalogique doit utiliser des relations dirigées. De cette façon, vous n'aurez pas de cycle.

8

Les données généalogiques sont cycliques et ne s'intègrent pas dans un graphique acyclique. Par conséquent, si vous avez des assertions contre des cycles, vous devez les supprimer.

Pour gérer cela dans une vue sans créer de vue personnalisée, vous devez traiter le parent cyclique en tant que parent "fantôme". En d'autres termes, lorsqu'une personne est à la fois père et grand-père de la même personne, le nœud grand-père est affiché normalement, mais le nœud père est restitué sous la forme d'un nœud "fantôme" portant une étiquette simple comme "voir grand-père". ) et pointe vers le grand-père.

Pour effectuer des calculs, vous devrez peut-être améliorer votre logique afin de gérer les graphiques cycliques de sorte qu'un nœud ne soit pas visité plus d'une fois s'il y a un cycle.

5
Tyler Durden

Les affirmations ne survivent pas à la réalité

Habituellement, les assertions ne survivent pas au contact des données du monde réel. Le processus de l’ingénierie logicielle fait partie du processus de décision, avec quelles données vous voulez traiter et lesquelles sont hors de portée.

Graphiques de famille cycliques

En ce qui concerne les "arbres" familiaux (ce sont en fait des graphiques complets, cycles compris), voici une anecdote intéressante:

J'ai épousé une veuve qui avait une fille adulte. Mon père, qui nous a souvent rendu visite, est tombé amoureux de ma belle-fille et l'a épousée. En conséquence, mon père est devenu mon fils et ma fille est devenue ma mère. Quelque temps plus tard, j'ai donné à mon épouse un fils, qui était le frère de mon père, et mon oncle. La femme de mon père (qui est aussi ma fille et ma mère) a eu un fils. En conséquence, j'ai un frère et un petit-fils dans la même personne. Ma femme est maintenant ma grand-mère, car elle est la mère de ma mère. Je suis donc le mari de ma femme et en même temps le beau-petit-fils de ma femme. En d'autres termes, je suis mon grand-père.

Les choses deviennent encore plus étranges lorsque vous prenez en compte mères porteuses ou "paternité floue".

Comment gérer ça

Définir les cycles hors champ

Vous pouvez décider que votre logiciel ne doit pas traiter de tels cas rares. Si tel est le cas, l'utilisateur doit utiliser un produit différent. Cela rend beaucoup plus robuste le traitement des cas les plus courants, car vous pouvez conserver plus d'assertions et un modèle de données plus simple.

Dans ce cas, ajoutez de bonnes fonctionnalités d'importation et d'exportation à votre logiciel afin que l'utilisateur puisse facilement migrer vers un produit différent en cas de besoin.

Autoriser les relations manuelles

Vous pouvez autoriser l'utilisateur à ajouter des relations manuelles. Ces relations ne sont pas des "citoyens de première classe", c’est-à-dire que le logiciel les prend telles quelles, ne les vérifie pas et ne les gère pas dans le modèle de données principal.

L'utilisateur peut alors gérer les cas rares à la main. Votre modèle de données restera toujours assez simple et vos affirmations survivront.

Soyez prudent avec les relations manuelles. Il est tentant de les rendre complètement configurables et donc de créer un modèle de données entièrement configurable. Cela ne fonctionnera pas: votre logiciel ne sera pas mis à l'échelle, vous obtiendrez des bugs étranges et l'interface utilisateur deviendra inutilisable. Cet anti-motif s'appelle "soft coding" , et "Le WTF quotidien" est plein d'exemples pour cela.

Rendez votre modèle de données plus flexible, ignorez les assertions, testez les invariants

Le dernier recours serait de rendre votre modèle de données plus flexible. Il vous faudrait presque toutes les assertions et ignorer votre modèle de données sur un graphique complet. Comme le montre l'exemple ci-dessus, il est facilement possible d'être votre propre grand-père, de sorte que vous pouvez même avoir des cycles.

Dans ce cas, vous devez tester votre logiciel de manière approfondie. Vous avez dû ignorer presque toutes les assertions, il y a donc de bonnes chances que des bogues supplémentaires apparaissent.

Utilisez un générateur de données de test pour vérifier les cas de test inhabituels. Il existe des bibliothèques de contrôle rapide pour Haskell , Erlang ou C . Pour Java/Scala, il y a ScalaCheck et Nyaya . Une idée de test serait de simuler une population aléatoire, de la laisser se croiser de manière aléatoire, puis de laisser votre logiciel importer puis exporter le résultat. On s'attend à ce que toutes les connexions de la sortie se trouvent également à l'entrée et au vers.

Un cas où une propriété reste la même s'appelle un invariant. Dans ce cas, l'invariant est l'ensemble des "relations amoureuses" entre les individus de la population simulée. Essayez de trouver autant d'invariants que possible et testez-les avec des données générées aléatoirement. Les invariants peuvent être fonctionnels, par exemple:

  • un oncle reste un oncle, même si vous ajoutez d'autres "relations amoureuses"
  • chaque enfant a un parent
  • une population de deux générations a au moins un grand-parent

Ou ils peuvent être techniques:

  • Votre logiciel ne plantera pas sur un graphique représentant jusqu'à 10 milliards de membres (peu importe le nombre d'interconnexions)
  • Votre logiciel évolue avec O(number-of-nodes) et O (nombre d'arêtes ^ 2)
  • Votre logiciel peut sauvegarder et recharger chaque graphe familial jusqu'à 10 milliards de membres

En exécutant les tests simulés, vous trouverez beaucoup de cas étranges. Leur réparation prendra beaucoup de temps. En outre, vous perdrez beaucoup d'optimisations, votre logiciel fonctionnera beaucoup plus lentement. Vous devez décider si cela en vaut la peine et si cela entre dans le cadre de votre logiciel.

4
stefan.schwetschke

La chose la plus importante est de avoid creating a problem, donc je crois que vous devriez tiliser une relation directe pour éviter d'avoir un cycle.

Comme @markmywords l'a dit, # include "fritzl.h".

Enfin, je dois dire recheck your data structure. Peut-être que quelque chose ne va pas là-bas (peut-être une liste chaînée bidirectionnelle résout votre problème).

4
Nasser Hadjloo

Au lieu de supprimer toutes les assertions, vous devriez tout de même vérifier si une personne est son propre parent ou toute autre situation impossible et présenter une erreur. Peut-être émettre un avertissement s'il est peu probable que l'utilisateur puisse toujours détecter les erreurs de saisie courantes, mais cela fonctionnera si tout est correct.

Je voudrais stocker les données dans un vecteur avec un entier permanent pour chaque personne et stocker les parents et les enfants dans des objets de personne où ledit int est l'indice du vecteur. Ce serait assez rapide pour passer d'une génération à l'autre (mais lent pour des choses comme les recherches de nom). Les objets seraient dans l'ordre de leur création.

3
ctype.h