Fix Deep Relationship filter to avoid recursion crash

Fixes #10279
This also changes filter to use shortest paths.
This commit is contained in:
prculley 2017-11-22 08:29:11 -06:00 committed by Nick Hall
parent 85014bd92c
commit e7bc887f60

View File

@ -3,6 +3,7 @@
# #
# Copyright (C) 2009 Robert Ham <rah@bash.sh> # Copyright (C) 2009 Robert Ham <rah@bash.sh>
# Copyright (C) 2011 Adam Stein <adam@csh.rit.edu> # Copyright (C) 2011 Adam Stein <adam@csh.rit.edu>
# Copyright (C) 2017 Paul Culley <paulr2787@gmail.com>
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -24,6 +25,7 @@
# Standard Python modules # Standard Python modules
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
from collections import deque
from ....const import GRAMPS_LOCALE as glocale from ....const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext _ = glocale.translation.gettext
@ -95,31 +97,52 @@ def get_person_family_people(db, person, person_handle):
return people return people
def find_deep_relations(db, user, person, path, seen, target_people):
if len(target_people) < 1:
return []
def find_deep_relations(db, user, person, target_people):
""" This explores all possible paths between a person and one or more
targets. The algorithm processes paths in a breadth first wave, one
remove at a time. The first path that reaches a target causes the target
to be marked complete and the path to be stored in the return_paths set.
By processing in wave, the return path should be a shortest path.
The function stores to do data and intermediate results in an ordered dict,
rather than using a recursive algorithm becasue some trees have been found
that exceed the standard python recursive depth. """
return_paths = set() # all people in paths between targets and person
if person is None: if person is None:
return [] return return_paths
todo = deque([person.handle]) # list of work to do, handles, add to right,
# pop from left
done = {} # The key records handles already examined,
# the value is a handle of the previous person in the path, or None at
# head of path. This forms a linked list of handles along the path.
done[person.handle] = None
while todo:
handle = todo.popleft()
handle = person.get_handle()
if handle in seen:
return []
seen.append(handle)
if user: if user:
user.step_progress() user.step_progress()
return_paths = [] if handle in target_people: # if we found a target
person_path = path + [handle] prev_hndl = handle
# Go through linked list and save the handles in return_paths
if handle in target_people: while prev_hndl:
return_paths += [person_path] return_paths.add(prev_hndl)
prev_hndl = done[prev_hndl]
target_people.remove(handle) target_people.remove(handle)
if not target_people: # Quit searching if all targets found
break
family_people = get_person_family_people(db, person, handle) person = db.get_person_from_handle(handle)
for family_person in family_people: if person is None:
pers = db.get_person_from_handle(family_person) continue
return_paths += find_deep_relations(db, user, pers, person_path, seen, target_people)
people = get_person_family_people(db, person, handle)
for p_hndl in people:
if p_hndl in done: # check if we have already been here
continue # and ignore if we have
todo.append(p_hndl) # Add to the todo list
done[p_hndl] = handle # Add to the (almost) done list
return return_paths return return_paths
@ -147,12 +170,12 @@ class DeepRelationshipPathBetween(Rule):
user.begin_progress(_('Finding relationship paths'), user.begin_progress(_('Finding relationship paths'),
_('Evaluating people'), _('Evaluating people'),
db.get_number_of_people()) db.get_number_of_people())
paths = find_deep_relations(db, user, root_person, [], [], target_people) self.__matches = find_deep_relations(db, user, root_person, target_people)
if user: if user:
user.end_progress() user.end_progress()
self.__matches = set() # self.__matches = set()
list(map(self.__matches.update, paths)) # list(map(self.__matches.update, paths))
def reset(self): def reset(self):
self.__matches = set() self.__matches = set()