Ancestor and Descendant List Creation (code snippet)

Description

Each main function returns a table containing a list of Ancestors or Descendants or All Relatives for a given root Individual. They operate by traversing the Family record links for Husband, Wife, and Children.

The first set of functions employ a looping method, and the second set employs recursion.

It would be possible to call the built in functions IsAncestorOf or IsDescendantOf or IsRelativeOf or RelationPool, but they are slower in execution, especially if the given Individual is not the File Root.

Requires: None

Looping Method

relations.fh_lua
--[[
@function: CheckDuplicate
@description: Adds Record Id as value and index, to table if it does not already exist
@parameters: Item Pointer - Must be at Record Level
@returns: true if pointer is a duplicate
@requires: none
]] 
function CheckDuplicate(table, ptr)
  local id = fhGetRecordId(ptr)
  if table[id] == nil then
      table[id] = id
      return false
      else
      return true
  end
end
-- End Function
--[[
@function: GetAncestorList
@description: Returns a list of Ancestors
@parameters: Item Pointer - Must be at Record Level
@returns: table of record Item Pointers
@requires: CheckDuplicate
]] 
function GetAncestorList(ptr)
    local ancestorlist  = {}
    local dupcheck = {}
 
    local ptrMother = fhNewItemPtr()
    local ptrFather = fhNewItemPtr()
    local ptrFamily = fhNewItemPtr()
 
    table.insert(ancestorlist,ptr:Clone())
    CheckDuplicate(dupcheck,ptr)
    iLoop = 1
    while iLoop <= #ancestorlist do
        ptrBase = ancestorlist[iLoop]
        -- Loop Family as Child
        ptrFamily:MoveTo(ptrBase,'~.FAMC')
        while ptrFamily:IsNotNull() do
            ptrMother:MoveTo(fhGetValueAsLink(ptrFamily),'~.WIFE>')
            if ptrMother:IsNotNull() and (not CheckDuplicate(dupcheck,ptrMother))then
                table.insert(ancestorlist,ptrMother:Clone())
            end
            ptrFather:MoveTo(fhGetValueAsLink(ptrFamily),'~.HUSB>')
            if ptrFather:IsNotNull() and (not CheckDuplicate(dupcheck,ptrFather)) then
                table.insert(ancestorlist,ptrFather:Clone())
            end
            ptrFamily:MoveNext('SAME_TAG')
        end
        iLoop = iLoop + 1
    end
    return ancestorlist
end
-- End Function
--[[
@function: GetDecendantList
@description: Returns a list of decendants
@parameters: Item Pointer - Must be at Record Level
@returns: table of record Item Pointers
@requires: CheckDuplicate
]] 
function GetDecendantList(ptr)
    local decendantlist  = {}
    local dupcheck = {}
    local ptrChild = fhNewItemPtr()
 
    local ptrFamily = fhNewItemPtr()
    local ptrFamilyRec = fhNewItemPtr()
    local ptrBase   = fhNewItemPtr
 
    table.insert(decendantlist,ptr:Clone())
    CheckDuplicate(dupcheck,ptr)
    iLoop = 1
    while iLoop <= #decendantlist do
        ptrBase = decendantlist[iLoop]
        -- Loop Family as Spouse
        ptrFamily:MoveTo(ptrBase,'~.FAMS')
        while ptrFamily:IsNotNull() do
            ptrFamilyRecord = fhGetValueAsLink(ptrFamily)
            -- Loop Children
            ptrChild:MoveTo(ptrFamilyRecord,'~.CHIL')
            while ptrChild:IsNotNull() do 
              ptrChildRecord = fhGetValueAsLink(ptrChild)
                if ptrChildRecord:IsNotNull() and not CheckDuplicate(dupcheck,ptrChildRecord) then
                    table.insert(decendantlist,fhGetValueAsLink(ptrChild))
                end
                ptrChild:MoveNext('SAME_TAG')
            end
            ptrFamily:MoveNext('SAME_TAG')
        end
        iLoop = iLoop + 1
    end
    return decendantlist
end
-- End Function

Recursive Method

The local functions call themselves recursively, which avoids the iLoop counter. Effectively, each call is for the next generation.

relatives.fh_lua
--[[
@Function:	NewRelative
@Description:	Adds Record Id and Pointer to table if it does not already exist
@Parameters:	Record Pointer, Dictionary of Record Id, Array of Record Id & Pointers
@Returns:	True if Pointer exists and not already listed, else returns False
@Requires:	None
]] 
function NewRelative(ptrRec,dicRel,arrRel)
	if ptrRec:IsNotNull() then
		local intRec = fhGetRecordId(ptrRec)
		if not dicRel[intRec] then
			dicRel[intRec] = true
			table.insert(arrRel,{ Id=intRec; Rec=ptrRec:Clone(); })
			-- Prevent Stopped Working/Not Responding message for large trees
			if #arrRel % 1000 == 0 then fhSleep(10,7) end
			return true
		end
	end
	return false
end -- function NewRelative
 
--[[
@Function:	GetAncestorList
@Description:	Gets a list of Ancestors
@Parameters:	Record Pointer
@Returns:	Array holding Ancestor Record Id and Pointers
@Requires:	None
]] 
function GetAncestorList(ptrRec)
	local arrRelative = { }						-- Array of Individual Record Id & Pointers
	local dicRelative = { }						-- Dictionary of processed Individual Record Id
 
	local function getAncestor(ptrRec)				-- Get next Ancestor generation
		if NewRelative(ptrRec,dicRelative,arrRelative) then
			local ptrFamc = fhGetItemPtr(ptrRec,"~.FAMC")	-- Cater for multiple parent families
			while ptrFamc:IsNotNull() do			-- Cater for both sex & same sex parents
				local ptrFam = fhGetValueAsLink(ptrFamc)
				getAncestor(fhGetItemPtr(ptrFam,"~.HUSB[1]>"))
				getAncestor(fhGetItemPtr(ptrFam,"~.WIFE[1]>"))
				getAncestor(fhGetItemPtr(ptrFam,"~.HUSB[2]>"))
				getAncestor(fhGetItemPtr(ptrFam,"~.WIFE[2]>"))
				ptrFamc:MoveNext("SAME_TAG")
			end
		end
	end -- local function getAncestor
 
	getAncestor(ptrRec)
	return arrRelative
end -- function GetAncestorList
 
--[[
@Function:	GetDescendantList
@Description:	Gets a list of Descendants
@Parameters:	Record Pointer
@Returns:	Array holding Descendant Record Id and Pointers
@Requires:	None
]] 
function GetDescendantList(ptrRec)
	local arrRelative = { }						-- Array of Individual Record Id & Pointers
	local dicRelative = { }						-- Dictionary of processed Individual Record Id
 
	local function getDescends(ptrRec)				-- Get next Descendant generation
		if NewRelative(ptrRec,dicRelative,arrRelative) then
			local ptrFams = fhGetItemPtr(ptrRec,"~.FAMS")	-- Cater for multiple spouse families
			while ptrFams:IsNotNull() do			-- Cater for both sex & same sex partners
				local ptrFam = fhGetValueAsLink(ptrFams)
				getDescends(fhGetItemPtr(ptrFam,"~.HUSB[1]>"))
				getDescends(fhGetItemPtr(ptrFam,"~.WIFE[1]>"))
				getDescends(fhGetItemPtr(ptrFam,"~.HUSB[2]>"))
				getDescends(fhGetItemPtr(ptrFam,"~.WIFE[2]>"))
				local ptrChil = fhGetItemPtr(ptrFam,"~.CHIL")
				while ptrChil:IsNotNull() do		-- Get all the children
					getDescends(fhGetValueAsLink(ptrChil))
					ptrChil:MoveNext("SAME_TAG")
				end
				ptrFams:MoveNext("SAME_TAG")
			end
		end
	end -- local function getDescends
 
	getDescends(ptrRec)
	return arrRelative
end -- function GetDescendantList
 
--[[
@Function:	GetRelationList
@Description:	Gets a list of all Relations, i.e. in same Pool
@Parameters:	Record Pointer
@Returns:	Array holding Relations Record Id and Pointers
@Requires:	None
]] 
function GetRelationList(ptrRec)
	local arrRelative = { }						-- Array of Individual Record Id & Pointers
	local dicRelative = { }						-- Dictionary of processed Individual Record Id
	local dicFamilies = { }						-- Dictionary of processed Family Record Id
 
	local function getRelation(ptrRec)				-- Get next Relationship generation
		if NewRelative(ptrRec,dicRelative,arrRelative) then
			for _, strFamx in ipairs ({ "~.FAMC"; "~.FAMS"; }) do	-- Cater for all parent/spouse families
				local ptrFamx = fhGetItemPtr(ptrRec,strFamx)
				while ptrFamx:IsNotNull() do
					local ptrFam = fhGetValueAsLink(ptrFamx)
					local intFam = fhGetRecordId(ptrFam)
					if not dicFamilies[intFam] then		-- New family Record Id
						dicFamilies[intFam] = true	-- Cater for both sex & same sex partners
						getRelation(fhGetItemPtr(ptrFam,"~.HUSB[1]>"))
						getRelation(fhGetItemPtr(ptrFam,"~.WIFE[1]>"))
						getRelation(fhGetItemPtr(ptrFam,"~.HUSB[2]>"))
						getRelation(fhGetItemPtr(ptrFam,"~.WIFE[2]>"))
						local ptrChil = fhGetItemPtr(ptrFam,"~.CHIL")
						while ptrChil:IsNotNull() do	-- Get all children
							getRelation(fhGetValueAsLink(ptrChil))
							ptrChil:MoveNext("SAME_TAG")
						end
					end
					ptrFamx:MoveNext("SAME_TAG")
				end
			end
		end
	end -- local function getRelation
 
	getRelation(ptrRec)
	return arrRelative
end -- function GetRelationList

Usage

local ptrRoot = fhCallBuiltInFunction("FileRoot")
local tblAncs = GetAncestorList(ptrRoot)
local tblDesc = GetDescendantList(ptrRoot)
local tblRels = GetRelationList(ptrRoot)

Then for recursive versions, that return more complex tables, they can be sorted and simplified.

table.sort(tblRel,function(tblA,tblB) return tblA.Id < tblB.Id end)
for intIndi = 1, #tblRels do
	tblRels[intIndi] = tblRels[intIndi].Rec
end