Циклы в программном обеспечении генеалогического древа


Я являюсь разработчиком некоторого программного обеспечения для генеалогического дерева (написанного на C++ и Qt). У меня не было никаких проблем, пока один из моих клиентов не отправил мне сообщение об ошибке. Проблема в том, что у клиента двое детей с собственной дочерью, и, как следствие, он не может использовать мое программное обеспечение из-за ошибок.

эти ошибки являются результатом моих различных утверждений и инвариантов о обрабатываемом семейном графе (например, после прохождения цикла программа утверждает, что X не может быть одновременно отцом и дедушка г).

Как я могу устранить эти ошибки, не удаляя все утверждения данных?

18 1594

18 ответов:

похоже, у вас (и / или вашей компании) есть фундаментальное непонимание того, что такое генеалогическое древо.

позвольте мне уточнить, я также работаю в компании, которая имеет (как один из своих продуктов) генеалогическое древо в своем портфеле, и мы боролись с подобными проблемами.

проблема, в нашем случае, и я предполагаю, что ваш случай также, исходит из GEDCOM формат, который является чрезвычайно самоуверенным о том, что семья должна быть. Однако это формат содержит некоторые серьезные заблуждения о том, как на самом деле выглядит генеалогическое древо.

У GEDCOM есть много проблем, таких как несовместимость с однополыми отношениями, инцест и т. д... Что в реальной жизни происходит чаще, чем вы себе представляете (особенно когда возвращаетесь во времени к 1700-1800).

мы смоделировали наше генеалогическое древо с тем, что происходит в реальном мире: события (например, рождения, свадьбы, помолвки, союзы, смерти, усыновления и т. д.). Мы не ставим никаких ограничения на них, за исключением логически невозможных (например, нельзя быть собственным родителем, отношения нужны два человека и т. д.)...)

отсутствие валидаций дает нам более "реальный мир", более простое и гибкое решение.

Что касается этого конкретного случая, я бы предложил удалить утверждения, поскольку они не являются универсальными.

для отображения проблем (которые возникнут) я бы предложил рисовать один и тот же узел столько раз, сколько необходимо, намекая при дублировании загораются все копии при выборе одной из них.

расслабьте свои утверждения.

Не путем изменения правил, которые, скорее всего, очень полезны для 99,9% ваших клиентов в ловле ошибок при вводе своих данных.

вместо этого измените его с ошибки " не удается добавить отношения "на предупреждение с"Добавить в любом случае".

вот проблема с генеалогическими деревьями: они не деревья. Они являются направленными ациклическими графами или DAG. Если я правильно понимаю принципы биологии размножения человека, то никаких циклов не будет.

насколько я знаю, даже христиане принимают браки (и, следовательно, детей) между двоюродными братьями и сестрами, что превратит генеалогическое древо в семейный Даг.

мораль этой истории такова: выбирайте правильные структуры данных.

Я думаю, что у вас есть некоторое значение, которое однозначно идентифицирует человека, на которых вы можете основывать ваши чеки.

это сложный вопрос. Если вы хотите сохранить структуру дерева, я предлагаю этот:

допустим этот: A дети с собственной дочерью.

A добавляет себя в программе как A и B. Оказавшись в роли отца, назовем его парнем.

добавить is_same_for_out() функция, которая сообщает выход создание части вашей программы, что все ссылки собираются B внутренне должен идти A о представлении данных.

это позволит сделать некоторую дополнительную работу для пользователя, но я думаю, что это будет относительно легко реализовать и поддерживать.

исходя из этого, вы можете работать над синхронизацией кода A и B чтобы избежать нестыковок.

это решение, конечно, не идеально, но это первый подход.

вы должны сосредоточиться на что действительно имеет значение для вашего программного обеспечения. Стоит ли время, потраченное на то, чтобы заставить его работать на одного потребителя, цены лицензии ? Скорее всего, нет.

Я советую вам извиниться перед этим клиентом, сказать ему, что его ситуация выходит за рамки вашего программного обеспечения и выдать ему возмещение.

вы должны установить Атрейдес семья (либо современный, Дюна, или древние, Царь Эдип) в качестве тестового случая. Вы не найдете ошибок, используя очищенные данные в качестве тестового случая.

Это одна из причин, почему такие языки, как "Go", не имеют утверждений. Они используются для обработки случаев, о которых вы, вероятно, не думали, слишком часто. вы должны утверждать только невозможное, а не просто маловероятное. Выполнение последнего-это то, что дает утверждениям плохую репутацию. Каждый раз, когда вы набираете assert(, отойди на десять минут и действительно подумайте об этом.

в вашем особенно тревожном случае это и мыслимо, и ужасно что такое утверждение было бы фальшивым при редких, но возможных обстоятельствах. Следовательно, обработайте его в своем приложении, если только сказать:"это программное обеспечение не было разработано для обработки сценария, который вы представили".

утверждать, что ваш великий, великий, великий дед был вашим отцом, как невозможно, - это разумная вещь.

Если бы я работал в тестирующей компании, которая была нанята для тестирования вашего программного обеспечения, конечно, я бы представил этот сценарий. Зачем? Каждый несовершеннолетний еще интеллектуальный "пользователь" собирается сделать то же самое и смак в результате "отчет об ошибке".

Я ненавижу комментировать такую запутанную ситуацию, но самый простой способ не перестраивать все ваши инварианты-создать фантомную вершину в вашем графе, которая действует как прокси обратно к кровосмесительному папе.

Итак, я сделал некоторую работу над программным обеспечением генеалогического древа. Я думаю, что проблема, которую вы пытаетесь решить, заключается в том, что вам нужно иметь возможность ходить по дереву, не попадая в бесконечные петли - другими словами, дерево должно быть ациклическим.

однако, похоже, вы утверждаете, что есть только один путь между человеком и одним из его предков. Это будет гарантировать, что нет циклов, но слишком строго. Биологически говоря, потомство-это направленный ациклический граф (DAG). Случай, который у вас есть, конечно, вырожденный случай, но такие вещи происходят все время на больших деревьях.

например, если вы посмотрите на 2^n предков, которые у вас есть в поколении n, если бы не было перекрытия, то у вас было бы больше предков в 1000 году нашей эры, чем было живых людей. Значит, должно быть перекрытие.

тем не менее, вы также склонны получать циклы, которые являются недействительными, просто плохие данные. Если вы пересекаете дерево, то циклы должны быть обработаны с. Вы можете сделать это в каждом отдельном алгоритме или при загрузке. Я сделал это на нагрузке.

найти истинные циклы в дереве можно несколькими способами. Неправильный способ-пометить каждого предка от данного индивидуума, и при прохождении, если человек, к которому вы собираетесь перейти, уже отмечен, то разрежьте ссылку. Это разорвет потенциально точные отношения. Правильный способ сделать это-начать с каждого индивидуума и отметить каждого предка путем к этому индивидууму. Если Новый Путь содержит текущий путь как подпуть, то это цикл, и должны быть сломаны. Вы можете хранить пути как vector (MFMF, MFFFMF и т. д.) что делает сравнение и хранение очень быстро.

есть несколько других способов обнаружения циклов, таких как отправка двух итераторов и просмотр, если они когда-либо сталкиваются с тестом подмножества, но я в конечном итоге использовал метод локального хранения.

Также обратите внимание, что вам не нужно на самом деле разорвать ссылку, вы можете просто изменить его с a обычная ссылка на "слабую" ссылку, за которой не следуют некоторые из ваших алгоритмов. Вы также захотите позаботиться о том, чтобы выбрать, какую ссылку отметить как слабую; иногда вы можете выяснить, где цикл должен быть нарушен, глядя на информацию о дате рождения, но часто вы ничего не можете понять, потому что так много данных отсутствует.

еще один насмешливый серьезный ответ на глупый вопрос:

реальный ответ, использовать соответствующую структуру данных. Человеческая генеалогия не может быть полностью выражена с помощью чистого дерева без циклов. Вы должны использовать какой-то график. Кроме того, поговорите с антропологом, прежде чем идти дальше с этим, потому что есть много других мест, где подобные ошибки могут быть сделаны, пытаясь смоделировать генеалогию, даже в самом простом случае "Западного патриархального моногамного брака"."

даже если мы хотим игнорировать локально табуированные отношения, как описано здесь, существует множество совершенно законных и совершенно неожиданных способов введения циклов в генеалогическое древо.

например:http://en.wikipedia.org/wiki/Cousin_marriage

в основном, браки с двоюродными братьями не только распространены и ожидаемы, это причина, по которой люди перешли от тысяч небольших семейных групп к мировому населению в 6 миллиардов человек. Он не может работать любой другой способ.

там действительно очень мало универсалий, когда дело доходит до родословной, семьи и рода. Почти любое строгое предположение о нормах, предполагающих, кто может быть тетей, или кто может жениться на ком, или как дети узаконены с целью наследования, может быть нарушено каким-то исключением где-то в мире или истории.

потенциальные правовые последствия в сторону, это, конечно, кажется, что вам нужно рассматривать "узел" на генеалогическом древе в качестве предшественника человека, а не предполагать, что узел может быть один и только человек.

пусть узел дерева включает человека, а также преемников-и тогда вы можете иметь другой узел глубже по дереву, который включает в себя того же человека с разными преемниками.

несколько ответов показали способы сохранения утверждений / инвариантов, но это похоже на неправильное использование утверждений/инвариантов. Утверждения должны быть уверены, что то, что должно быть истинным, истинно, а инварианты должны быть уверены, что то, что не должно меняться, не меняется.

вы утверждаете, что кровосмесительных отношений не существует. Ясно, что они do существует, поэтому ваше утверждение недействительно. Вы можете обойти это утверждение, но реальная ошибка в само утверждение. Утверждение должно быть удалено.

ваше генеалогическое древо должно использовать направленные отношения. Таким образом, у вас не будет цикла.

генеалогические данные цикличны и не вписываются в ациклический граф, поэтому, если у вас есть утверждения против циклов, вы должны их удалить.

способ обработки этого в представлении без создания пользовательского представления заключается в том, чтобы рассматривать циклический родитель как "призрачный" родитель. Другими словами, когда человек является и отцом, и дедушкой для одного и того же человека, то узел дедушки отображается нормально, но узел отца отображается как "призрак" узел, который имеет простую метку, например ("см дедушка") и указывает на дедушку.

для выполнения вычислений вам может потребоваться улучшить свою логику для обработки циклических графиков, чтобы узел не посещался более одного раза, если есть цикл.

самое главное-это avoid creating a problem, Так что я считаю, что вы должны используйте прямое отношение чтобы избежать цикл.

как сказал @markmywords,#включить "fritzl.ч."

наконец-то я должен сказать recheck your data structure. Может быть, что-то там не так (может быть, двунаправленный связанный список решает вашу проблему).

утверждения не выдерживают реальности

обычно утверждения не выдерживают контакта с данными реального мира. Это часть процесса разработки программного обеспечения, чтобы решить, с какими данными вы хотите иметь дело и которые выходят за рамки.

циклические семейные графы

Что касается семейных "деревьев" (на самом деле это полномасштабные графики, включая циклы), есть хороший анекдот:

Я женился на вдове, у которой была взрослая дочь. Мой отец, который часто посещал нас, влюбился в мою падчерицу и женился на ней. В результате мой отец стал моим сыном, а моя дочь-моей матерью. Некоторое время спустя я подарил жене сына, который был братом моего отца и моего дяди. Жена моего отца (которая также является моей дочерью и моей матерью) получила сына. В результате я получил брата и внука в одном лице. Моя жена моя бабушка, потому что она мать моей матери. Так что я муж моей жены, и в то же время приемный внук моей жены. Другими словами, Я сам себе дедушка.

все становится еще более странным, когда вы берете суррогаты или "нечеткое отцовство" во внимание.

Как с этим бороться

определить циклы как вне области

вы можете решить, что ваше программное обеспечение не должно иметь дело с такими редкими случаями. В этом случае пользователь должен использовать другой продукт. Это делает работу с более распространенными случаями намного более надежной, потому что вы можете сохранить несколько утверждений, и более простую модель данных.

в этом случае добавьте некоторые хорошие функции импорта и экспорта в ваше программное обеспечение, чтобы пользователь мог легко перейти на другой продукт, когда это необходимо.

разрешить ручные отношения

вы можете разрешить пользователю добавлять ручные отношения. Эти отношения не являются "первоклассными гражданами", т. е. программное обеспечение принимает их как есть, не проверяет их и не обрабатывает в основной модели данных.

пользователь может обрабатывать редкие случаи вручную. Ваша модель данных по-прежнему будет оставаться довольно простой, и ваши утверждения выживут.

будьте осторожны с ручным отношений. Существует соблазн сделать их полностью настраиваемыми и, следовательно, создать полностью настраиваемую модель данных. Это не сработает: ваше программное обеспечение не будет масштабироваться, вы получите странные ошибки и, наконец, пользовательский интерфейс станет непригодным для использования. Этот анти-паттерн называется "мягкое кодирование", и "The daily WTF" полно примеров для этого.

Сделайте свою модель данных более гибкой, пропустите утверждения, проверьте инварианты

последним средством было бы сделать вашу модель данных более гибкой. Вам придется пропустить почти все утверждения и основывать свою модель данных на полномасштабном графике. Как показывает приведенный выше пример, легко можно быть вашим собственным дедушкой, поэтому у вас даже могут быть циклы.

в этом случае, вы должны тщательно проверить ваш программное обеспечение. Вы должны были пропустить почти все утверждения, так что есть хороший шанс для дополнительных ошибок.

используйте генератор тестовых данных для проверки необычных тестовых случаев. Есть быстрая проверка библиотек для Haskell,Эрланг или C. Для Java / Scala есть ScalaCheck и Ньяя. Одной из идей теста было бы имитировать случайную популяцию, пусть она скрещивается случайным образом, а затем пусть ваше программное обеспечение сначала импортирует, а затем экспортируйте результат. Ожидание будет заключаться в том, что все соединения на выходе также находятся во входном и наоборот.

случай, когда свойство остается неизменным, называется инвариантом. В этом случае инвариантом является совокупность "романтических отношений" между индивидами в моделируемой популяции. Постарайтесь найти как можно больше инвариантов и проверить их с помощью случайно сгенерированных данных. Инварианты могут быть функциональными, например:

  • дядя остается дядей, даже когда вы добавляете больше "романтических отношений"
  • у каждого ребенка есть родитель
  • у населения с двумя поколениями есть по крайней мере один прародитель

или они могут быть технические:

  • ваша программа не рухнет на графике до 10 миллиардов членов (независимо от того, сколько соединений)
  • ваше программное обеспечение масштабируется с O (число узлов) и O(число ребер^2)
  • ваше программное обеспечение может сохранять и повторно загружать каждый семейный график до 10 миллиардов членов

запустив имитационные тесты, вы найдете много странных угловых случаев. Их устранение займет много времени. Также вы потеряете много оптимизаций, ваше программное обеспечение будет работать намного медленнее. Вы должны решить, стоит ли это и если это входит в сферу вашего программного обеспечения.

вместо того, чтобы удалять все утверждения, вы все равно должны проверять такие вещи, как человек, являющийся его собственным родителем или другими невозможными ситуациями, и представлять ошибку. Возможно, выдать предупреждение, если это маловероятно, так что пользователь все еще может обнаружить распространенные ошибки ввода, но он будет работать, если все правильно.

Я бы хранил данные в векторе с постоянным целым числом для каждого человека и хранил родителей и детей в личных объектах, где указанный int является индексом вектора. Это было бы довольно быстро, чтобы перейти между поколениями (но медленно для таких вещей, как поиск имен). Объекты будут в порядке, когда они были созданы.

дублировать отца (или использовать символическую ссылку/ссылку).

например, если вы используете иерархическую базу данных:

$ #each person node has two nodes representing its parents.
$ mkdir Family
$ mkdir Family/Son
$ mkdir Family/Son/Daughter
$ mkdir Family/Son/Father
$ mkdir Family/Son/Daughter/Father
$ ln -s Family/Son/Daughter/Father Family/Son/Father
$ mkdir Family/Son/Daughter/Wife
$ tree Family
Family
└── Son
    ├── Daughter
    │   ├── Father
    │   └── Wife
    └── Father -> Family/Son/Daughter/Father

4 directories, 1 file