Treat brothers as male individuals sharing at least one recorded parent, including half-brothers. Derive a sibling helper and then select the recorded sex:
siblings :: Database -> Person -> [Person]
siblings db p = unique [q | parent <- parents db p,
q <- children db parent, q /= p]
brothers :: Database -> Person -> [Person]
brothers db p = [q | q <- siblings db p, sex q == Male]The exclusion
q /= p prevents a male individual from being his own brother. Duplicate removal is needed because a full brother can be found through both parents. The membership condition isIf the intended convention is full brothers only, replace the shared-parent condition by a common mother and a common father in the recorded certificates. The definition above states the convention explicitly rather than silently discarding half-brothers.children :: Database -> Person -> [Person]
children db p = unique [baby | (baby,mother,father) <- db,
mother == p || father == p]Extract the two recorded parents from every matching certificate, and use duplicate removal so that repeated certificates do not repeat an individual. This helper is shared by the remaining Haskell definitions:
unique :: Eq a => [a] -> [a]
unique [] = []
unique (x:xs) = x : unique [y | y <- xs, y /= x]
parents :: Database -> Person -> [Person]
parents db p = unique [q | (baby,mother,father) <- db,
baby == p, q <- [mother,father]]Use first cousins: the children of the siblings of a recorded parent. This includes cousins through a parent's half-sibling under the convention above. In Haskell:
cousins :: Database -> Person -> [Person]
cousins db p = unique [q | parent <- parents db p,
auntOrUncle <- siblings db parent,
q <- children db auntOrUncle, q /= p]Selecting siblings of parents avoids the mistake of returning all people with a common grandparent, which would also return ordinary siblings. The explicit exclusion of
p is harmless on an ordinary consistent pedigree and prevents a self-result in unusual records. Multiple relationship paths give just one output entry after duplicate removal. Thusinvert :: [(a, a)] -> [(a, a)]
invert r = [(y, x) | (x, y) <- r]Thus , the converse relation. Swapping is a bijection, since doing it twice restores the original pair. Therefore distinct input pairs remain distinct; no equality constraint or duplicate removal is necessary.
Retain the first element, filter all its further occurrences out of the tail, and recurse:
removedup :: Eq a => [a] -> [a]
removedup [] = []
removedup (x:xs) = x : removedup (filter (/= x) xs)The boxed specification is . The Haskell type class
Eq is needed for comparing values. Structural induction on a finite list proves duplicate removal: filtering removes every further x, and the recursive result contains each remaining distinct value exactly once. In the worst case of all distinct values, successive filters give quadratic time complexity.Use the first binary relation for the outer generator, the second for the inner generator, and match their middle values. Different witnesses can generate the same output pair, so apply duplicate removal:
relcomp :: Eq a => [(a, a)] -> [(a, a)] -> [(a, a)]
relcomp r s = removedup [(a, b) | (a, c) <- r,
(d, b) <- s, c == d]The guards give precisely the existential witness required for composition of relations:Every output is sound by its generating witness, and every witness is visited for finite inputs. Duplicate removal converts the resulting enumeration back to the required representation by distinct pairs.
The answer depends on the actual definitions above, especially the direction used for duplicate removal.
invertworks for an infinite binary relation enumerated by a lazy list: each output needs only one input pair, and swapping preserves distinctness.removedupis productive whenever the input has infinitely many distinct values and equality tests terminate. Each next new value occurs at a finite input position and passes finitely many earlier filters. In particular, it works on the stipulated distinct-element representation of an infinite binary relation. On an infinite list with only finitely many distinct values it emits those values but cannot certify that there is no next new value; for example, demanding the tail after the first result ofremovedup (repeat 1)diverges.relcompdoes not work for arbitrary infinite inputs. Its nested list comprehension can remain forever in the inner generator. Withr = [(i,0) | i <- [0..]]ands = [(0,j) | j <- [0..]], it emits only pairs whose first component is zero, starving every later outer value. Duplicate removal cannot repair this lack of fair enumeration.transclosedoes not work for a general infinite input: even its firstlength scannot finish. It waits for finite saturation before returning any result.
Thus . A fairer enumeration may improve the latter constructions, but would be a different algorithm; ordinary depth-first enumeration does not become fair through lazy evaluation alone.