Sou desenvolvedor de algum software de árvore genealógica (escrito em C ++ e Qt). Não tive problemas até que um dos meus clientes me enviou um relatório de erro. O problema é que o cliente tem dois filhos com sua própria filha e, como resultado, ele não pode usar o meu software devido a erros.
Esses erros são o resultado de minhas várias afirmações e invariantes sobre o gráfico da família sendo processado (por exemplo, depois de andar um ciclo, o programa afirma que X não pode ser pai e avô de Y).
Como posso resolver esses erros sem remover todas as asserções de dados?
c++
graph
cycle
assertions
family-tree
Partick Höse
fonte
fonte
Respostas:
Parece que você (e / ou sua empresa) tem um mal-entendido fundamental sobre o que uma árvore genealógica deveria ser.
Deixe-me esclarecer, também trabalho para uma empresa que possui (como um de seus produtos) uma árvore genealógica em seu portfólio e estamos enfrentando problemas semelhantes.
O problema, no nosso caso, e presumo que você também seja, vem do formato GEDCOM , que é extremamente opinativo sobre o que uma família deve ser. No entanto, esse formato contém alguns equívocos severos sobre como uma árvore genealógica realmente se parece.
O GEDCOM tem muitos problemas, como incompatibilidade com relações do mesmo sexo, incesto, etc ... O que na vida real acontece com mais frequência do que você imagina (especialmente quando voltamos no tempo para 1700-1800).
Modelamos nossa árvore genealógica para o que acontece no mundo real: eventos (por exemplo, nascimentos, casamentos, noivado, sindicatos, mortes, adoções, etc.). Não impomos nenhuma restrição a isso, exceto as logicamente impossíveis (por exemplo, não se pode ser o pai ou a mãe, as relações precisam de dois indivíduos, etc.)
A falta de validações nos dá uma solução mais "real", mais simples e mais flexível.
Quanto a este caso específico, sugiro remover as afirmações, pois elas não se sustentam universalmente.
Para exibir problemas (que surgirão), sugiro desenhar o mesmo nó quantas vezes for necessário, sugerindo a duplicação iluminando todas as cópias ao selecionar uma delas.
fonte
Relaxe suas afirmações.
Não alterando as regras, que provavelmente são muito úteis para 99,9% de seus clientes na detecção de erros na inserção de dados.
Em vez disso, altere-o de um erro "não é possível adicionar relacionamento" para um aviso com um "adicionar mesmo assim".
fonte
Aqui está o problema com as árvores genealógicas: elas não são árvores. Eles são gráficos acíclicos direcionados ou DAGs. Se eu entender os princípios da biologia da reprodução humana corretamente, não haverá ciclos.
Até onde eu sei, até os cristãos aceitam casamentos (e, portanto, filhos) entre primos, o que transformará a árvore genealógica em um DAG da família.
A moral da história é: escolha as estruturas de dados corretas.
fonte
Eu acho que você tem algum valor que identifica exclusivamente uma pessoa na qual você pode basear seus cheques.
Este é um assunto complicado. Supondo que você queira manter a estrutura em uma árvore, sugiro o seguinte:
Suponha o seguinte:
A
tem filhos com sua própria filha.A
adiciona-se ao programa comoA
e comoB
. Uma vez no papel de pai, vamos chamá-lo de namorado.Adicione uma
is_same_for_out()
função que informe à parte geradora de saída de seu programa que todos os links que acessamB
internamente devem estarA
na apresentação de dados.Isso fará algum trabalho extra para o usuário, mas acho que a TI seria relativamente fácil de implementar e manter.
Com base nisso, você pode trabalhar na sincronização de código
A
eB
evitar inconsistências.Esta solução certamente não é perfeita, mas é uma primeira abordagem.
fonte
Você deve se concentrar no que realmente agrega valor ao seu software . O tempo gasto para fazê-lo funcionar para UM consumidor vale o preço da licença? Provavelmente não.
Aconselho que você peça desculpas a esse cliente, diga a ele que a situação dele está fora do escopo do seu software e emita um reembolso a ele.
fonte
Você deveria ter configurado a família Atreides (moderna, Dune ou antiga, Édipo Rex ) como um caso de teste. Você não encontra erros usando dados higienizados como um caso de teste.
fonte
Essa é uma das razões pelas quais idiomas como "Go" não possuem afirmações. Eles são usados para lidar com casos nos quais você provavelmente não pensou, com muita frequência. Você deve apenas afirmar o impossível, não simplesmente o improvável . Fazer o último é o que dá às afirmações uma má reputação. Toda vez que você digita
assert(
, sai por dez minutos e realmente pensa sobre isso.No seu caso particularmente perturbador, é ao mesmo tempo concebível e espantoso que tal afirmação seja falsa em circunstâncias raras, mas possíveis. Portanto, trate-o no seu aplicativo, apenas para dizer "Este software não foi projetado para lidar com o cenário que você apresentou".
Afirmar que seu tataravô ser seu pai como impossível é uma coisa razoável a se fazer.
Se eu estivesse trabalhando para uma empresa de testes contratada para testar seu software, é claro que eu teria apresentado esse cenário. Por quê? Todo "usuário" juvenil, porém inteligente, fará exatamente a mesma coisa e se deliciará com o "relatório de erro" resultante.
fonte
Eu odeio comentar sobre uma situação tão complicada, mas a maneira mais fácil de não rejeitar todos os seus invariantes é criar um vértice fantasma no seu gráfico que atua como um proxy para o pai incestuoso.
fonte
Então, eu fiz algum trabalho no software de árvore genealógica. Eu acho que o problema que você está tentando resolver é que você precisa ser capaz de andar na árvore sem entrar em ciclos infinitos - em outras palavras, a árvore precisa ser acíclica.
No entanto, parece que você está afirmando que há apenas um caminho entre uma pessoa e um de seus ancestrais. Isso garantirá que não haja ciclos, mas é muito rigoroso. Biologicamente falando, a descendência é um gráfico acíclico direcionado (DAG). O caso que você tem certamente é um caso degenerado, mas esse tipo de coisa acontece o tempo todo em árvores maiores.
Por exemplo, se você olhar para os 2 ^ n antepassados que você tem na geração n, se não houver sobreposição, terá mais ancestrais em 1000 dC do que pessoas vivas. Então, tem que haver sobreposição.
No entanto, você também tende a receber ciclos inválidos, apenas dados incorretos. Se você estiver atravessando a árvore, os ciclos deverão ser tratados. Você pode fazer isso em cada algoritmo individual ou em carga. Eu fiz isso em carga.
Encontrar ciclos verdadeiros em uma árvore pode ser feito de algumas maneiras. O caminho errado é marcar todos os antepassados de um determinado indivíduo e, ao atravessar, se a pessoa para a qual você vai passar já estiver marcada, corte o link. Isso cortará relacionamentos potencialmente precisos. A maneira correta de fazer isso é começar de cada indivíduo e marcar cada ancestral com o caminho para esse indivíduo. Se o novo caminho contiver o caminho atual como um subcaminho, será um ciclo e deverá ser interrompido. Você pode armazenar caminhos como vetor <bool> (MFMF, MFFFMF, etc.), o que torna a comparação e o armazenamento muito rápidos.
Existem algumas outras maneiras de detectar ciclos, como enviar dois iteradores e ver se eles colidem com o teste de subconjunto, mas acabei usando o método de armazenamento local.
Observe também que você não precisa realmente cortar o link, basta alterá-lo de um link normal para um link 'fraco', que não é seguido por alguns de seus algoritmos. Você também deve tomar cuidado ao escolher qual link marcar como fraco; Às vezes, você pode descobrir onde o ciclo deve ser interrompido observando as informações da data de nascimento, mas geralmente não é possível descobrir nada porque faltam muitos dados.
fonte
Outra resposta séria e falsa para uma pergunta boba:
A resposta real é, use uma estrutura de dados apropriada. A genealogia humana não pode ser totalmente expressa usando uma árvore pura sem ciclos. Você deve usar algum tipo de gráfico. Além disso, converse com um antropólogo antes de prosseguir com isso, porque há muitos outros lugares em que erros semelhantes poderiam ser cometidos na tentativa de modelar a genealogia, mesmo no caso mais simples de "casamento monogâmico patriarcal ocidental".
Mesmo se quisermos ignorar os relacionamentos tabus localmente, conforme discutido aqui, existem muitas maneiras perfeitamente legais e completamente inesperadas de introduzir ciclos em uma árvore genealógica.
Por exemplo: http://en.wikipedia.org/wiki/Cousin_marriage
Basicamente, o casamento entre primos não é apenas comum e esperado, é a razão pela qual os seres humanos passaram de milhares de pequenos grupos familiares para uma população mundial de 6 bilhões. Não pode funcionar de outra maneira.
Realmente existem muito poucos universais quando se trata de genealogia, família e linhagem. Quase qualquer suposição estrita sobre normas que sugerem quem pode ser uma tia, ou quem pode se casar com quem, ou como as crianças são legitimadas para fins de herança, pode ser perturbada por alguma exceção em algum lugar do mundo ou da história.
fonte
Possíveis implicações legais à parte, certamente parece que você precisa tratar um 'nó' em uma árvore genealógica como uma pessoa predecessora, em vez de assumir que o nó pode ser a única pessoa.
Faça com que o nó da árvore inclua uma pessoa e os sucessores - e então você poderá ter outro nó mais profundo na árvore que inclua a mesma pessoa com sucessores diferentes.
fonte
Algumas respostas mostraram maneiras de manter as asserções / invariantes, mas isso parece um uso indevido de asserções / invariantes. As asserções são para garantir que algo que deva ser verdadeiro seja verdadeiro, e os invariantes devem garantir que algo que não deve mudar não mude.
O que você está afirmando aqui é que não existem relacionamentos incestuosos. É claro que eles fazem existir, assim que sua afirmação é inválido. Você pode contornar essa afirmação, mas o bug real está na própria afirmação. A afirmação deve ser removida.
fonte
Sua árvore genealógica deve usar relações direcionadas. Dessa forma, você não terá um ciclo.
fonte
Os dados genealógicos são cíclicos e não se enquadram em um gráfico acíclico; portanto, se você tiver afirmações contra ciclos, deverá removê-las.
A maneira de lidar com isso em uma exibição sem criar uma exibição personalizada é tratar o pai cíclico como um pai "fantasma". Em outras palavras, quando uma pessoa é pai e avô da mesma pessoa, o nó do avô é mostrado normalmente, mas o nó do pai é renderizado como um nó "fantasma" que possui um rótulo simples como ("consulte o avô" ) e aponta para o avô.
Para fazer cálculos, pode ser necessário melhorar sua lógica para manipular gráficos cíclicos, para que um nó não seja visitado mais de uma vez se houver um ciclo.
fonte
O mais importante é
avoid creating a problem
, então acredito que você deve usar uma relação direta para evitar um ciclo.Como o @markmywords disse, #include "fritzl.h".
Finalmente eu tenho que dizer
recheck your data structure
. Talvez algo esteja errado por lá (talvez uma lista vinculada bidirecional resolva seu problema).fonte
Afirmações não sobrevivem à realidade
Normalmente, afirmações não sobrevivem ao contato com dados do mundo real. É parte do processo de engenharia de software decidir com quais dados você deseja lidar e quais estão fora do escopo.
Gráficos da família cíclica
Em relação às "árvores" da família (na verdade, são gráficos completos, incluindo ciclos), há uma boa anedota:
As coisas ficam ainda mais estranhas quando você leva em consideração os substitutos ou a "paternidade confusa".
Como lidar com isso
Definir ciclos como fora do escopo
Você pode decidir que seu software não deve lidar com casos tão raros. Se esse caso ocorrer, o usuário deve usar um produto diferente. Isso torna o tratamento dos casos mais comuns muito mais robusto, porque você pode manter mais asserções e um modelo de dados mais simples.
Nesse caso, adicione alguns bons recursos de importação e exportação ao seu software, para que o usuário possa migrar facilmente para um produto diferente quando necessário.
Permitir relações manuais
Você pode permitir que o usuário adicione relações manuais. Essas relações não são "cidadãos de primeira classe", ou seja, o software as aceita como estão, não as verifica e não as trata no modelo de dados principal.
O usuário pode então lidar com casos raros manualmente. Seu modelo de dados ainda permanecerá bastante simples e suas afirmações sobreviverão.
Tenha cuidado com as relações manuais. Existe uma tentação de torná-los completamente configuráveis e, portanto, criar um modelo de dados totalmente configurável. Isso não funcionará: seu software não será dimensionado, você receberá bugs estranhos e, finalmente, a interface do usuário ficará inutilizável. Esse anti-padrão é chamado de "codificação flexível" e "O diário WTF" está cheio de exemplos para isso.
Torne seu modelo de dados mais flexível, ignore asserções, teste invariáveis
O último recurso seria tornar seu modelo de dados mais flexível. Você precisaria pular quase todas as asserções e basear seu modelo de dados em um gráfico completo. Como mostra o exemplo acima, é facilmente possível ser seu próprio avô, assim você pode até ter ciclos.
Nesse caso, você deve testar extensivamente seu software. Você teve que pular quase todas as afirmações, para que haja uma boa chance de erros adicionais.
Use um gerador de dados de teste para verificar casos de teste incomuns. Há bibliotecas Verificação rápida para Haskell , Erlang ou C . Para Java / Scala, existem ScalaCheck e Nyaya . Uma idéia de teste seria simular uma população aleatória, deixá-la cruzar aleatoriamente, depois deixar o software primeiro importar e depois exportar o resultado. A expectativa seria que todas as conexões na saída também estivessem na entrada e vice-versa.
Um caso em que uma propriedade permanece a mesma é chamado de invariável. Nesse caso, o invariante é o conjunto de "relações românticas" entre os indivíduos na população simulada. Tente encontrar o máximo de invariantes possível e testá-los com dados gerados aleatoriamente. Invariantes podem ser funcionais, por exemplo:
Ou eles podem ser técnicos:
Ao executar os testes simulados, você encontrará muitos casos de canto estranhos. Consertá-los levará muito tempo. Além disso, você perderá muitas otimizações, seu software será executado muito mais lentamente. Você precisa decidir se vale a pena e se isso está no escopo do seu software.
fonte
Em vez de remover todas as asserções, você ainda deve verificar coisas como uma pessoa sendo seus próprios pais ou outras situações impossíveis e apresentar um erro. Talvez emita um aviso se for improvável, para que o usuário ainda possa detectar erros de entrada comuns, mas funcionará se tudo estiver correto.
Eu armazenaria os dados em um vetor com um número inteiro permanente para cada pessoa e os objetos de pais e filhos em pessoa, onde o dito int é o índice do vetor. Isso seria muito rápido entre gerações (mas lento para coisas como pesquisas de nome). Os objetos deveriam estar na ordem em que foram criados.
fonte
Duplique o pai (ou use o link simbólico / referência).
Por exemplo, se você estiver usando um banco de dados hierárquico:
fonte
ln -s
comando não funciona dessa maneira; a resolução do linkFamily/Son/Father
procurará porFamily/Son/Daughter/Father
baixoFamily/Son
, onde o link reside, não por.
onde você emitiu oln -s
comando.