-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtask_4.py
More file actions
91 lines (73 loc) · 2.46 KB
/
task_4.py
File metadata and controls
91 lines (73 loc) · 2.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
"""Task 4: Practice with recursion."""
import datetime
from pathlib import Path
from typing import Callable, Dict, Optional, Set
import pydantic
from typing_extensions import Literal
# --- models for family data
class Individual(pydantic.BaseModel, frozen=True):
"""
A model of an individual.
Parents, children, and spouses are keys to other
individual ids.
"""
id: str
name: str
surname: str
gender: Literal["M", "F"]
birth_year: Optional[int] = None
death_year: Optional[int] = None
parents: Set[str] = set()
children: Set[str] = set()
spouses: Set[str] = set()
@property
def age(self) -> Optional[int]:
"""Return the estimated age of the person."""
birth_year = self.birth_year
if birth_year is None or birth_year < 0:
raise ValueError(f"Unknown Age of {self.name}: {self.id}")
end_year = self.death_year
if end_year is None or end_year < 0:
end_year = datetime.datetime.now().year
return end_year - birth_year
class Group(pydantic.BaseModel):
"""A dict-like container for individuals."""
individuals: Dict[str, Individual] = {}
def __getitem__(self, item):
return self.individuals[item]
def load_data(path=None):
"""Loads the family tree data into memory."""
if path is None:
path = Path(__file__).absolute().parent / "data" / "family_tree.json"
return Group.parse_file(path)
def count_people(
individual: Individual,
group: Group,
direction: Literal["children", "parents"] = "parents",
predicate: Callable[[Individual], bool] = lambda x: True,
) -> int:
"""
Count people in an individuals family tree which meet some requirement.
Parameters
----------
individual
The person with which to start.
group
The group to which the person belongs.
direction
The direction to travel (down through children or up through parents).
predicate
A callable which take an individual as the only argument and returns
True or False if they meet the requirement.
Examples
--------
>>> import random
>>> data = load_data()
>>> # Count direct descendants with more than two children.
>>> person = data[random.choice(list(data))]
>>> count = count_people(person, predicate=lambda x: len(x.children) > 2)
Notes
-----
This function will also count the initial individual if they pass the
predicate.
"""