140 lines
4.4 KiB
Dart
140 lines
4.4 KiB
Dart
import 'dart:convert';
|
|
|
|
/// 성별 (호칭을 정할 때만 쓴다). 모르면 [unknown].
|
|
enum Gender {
|
|
male,
|
|
female,
|
|
unknown;
|
|
|
|
static Gender parse(String? s) => Gender.values.where((g) => g.name == s).firstOrNull ?? Gender.unknown;
|
|
}
|
|
|
|
/// 가계도의 한 사람. 출생 연도를 알면 형·동생, 큰아버지·작은아버지를 가른다.
|
|
class KinPerson {
|
|
const KinPerson(this.id, {this.gender = Gender.unknown, this.birthYear});
|
|
|
|
final String id;
|
|
final Gender gender;
|
|
final int? birthYear;
|
|
|
|
KinPerson copyWith({Gender? gender, int? birthYear}) =>
|
|
KinPerson(id, gender: gender ?? this.gender, birthYear: birthYear ?? this.birthYear);
|
|
|
|
Map<String, Object?> toJson() => {'id': id, 'g': gender.name, if (birthYear != null) 'y': birthYear};
|
|
|
|
factory KinPerson.fromJson(Map<String, Object?> j) =>
|
|
KinPerson(j['id'] as String, gender: Gender.parse(j['g'] as String?), birthYear: j['y'] as int?);
|
|
}
|
|
|
|
/// 가계도: 사람, 부모-자식, 배우자만 안다. 나는 [meId] 로 들어간다. 계산은 relation.dart 가 한다.
|
|
class KinGraph {
|
|
KinGraph();
|
|
|
|
static const meId = 'me';
|
|
|
|
final Map<String, KinPerson> _people = {};
|
|
final Map<String, Set<String>> _parents = {}; // 자식 → 부모들
|
|
final Map<String, Set<String>> _children = {}; // 부모 → 자식들
|
|
final Map<String, Set<String>> _spouses = {};
|
|
|
|
Iterable<KinPerson> get people => _people.values;
|
|
KinPerson? person(String id) => _people[id];
|
|
bool has(String id) => _people.containsKey(id);
|
|
|
|
Set<String> parentsOf(String id) => _parents[id] ?? const {};
|
|
Set<String> childrenOf(String id) => _children[id] ?? const {};
|
|
Set<String> spousesOf(String id) => _spouses[id] ?? const {};
|
|
|
|
void put(KinPerson p) => _people[p.id] = p;
|
|
|
|
/// [parent] 가 [child] 의 부모. 자기 자신이나 이미 후손인 사람을 부모로 두는 순환은 거절(false).
|
|
bool addParent(String child, String parent) {
|
|
if (child == parent || !has(child) || !has(parent)) return false;
|
|
if (_isAncestor(child, parent)) return false; // child 가 parent 의 조상이면 순환
|
|
if (parentsOf(child).length >= 2 && !parentsOf(child).contains(parent)) return false; // 부모는 최대 둘
|
|
(_parents[child] ??= {}).add(parent);
|
|
(_children[parent] ??= {}).add(child);
|
|
return true;
|
|
}
|
|
|
|
bool addSpouse(String a, String b) {
|
|
if (a == b || !has(a) || !has(b)) return false;
|
|
(_spouses[a] ??= {}).add(b);
|
|
(_spouses[b] ??= {}).add(a);
|
|
return true;
|
|
}
|
|
|
|
void removePerson(String id) {
|
|
for (final p in parentsOf(id).toList()) {
|
|
_children[p]?.remove(id);
|
|
}
|
|
for (final c in childrenOf(id).toList()) {
|
|
_parents[c]?.remove(id);
|
|
}
|
|
for (final s in spousesOf(id).toList()) {
|
|
_spouses[s]?.remove(id);
|
|
}
|
|
_parents.remove(id);
|
|
_children.remove(id);
|
|
_spouses.remove(id);
|
|
_people.remove(id);
|
|
}
|
|
|
|
void removeParent(String child, String parent) {
|
|
_parents[child]?.remove(parent);
|
|
_children[parent]?.remove(child);
|
|
}
|
|
|
|
void removeSpouse(String a, String b) {
|
|
_spouses[a]?.remove(b);
|
|
_spouses[b]?.remove(a);
|
|
}
|
|
|
|
bool _isAncestor(String maybeAncestor, String of) {
|
|
final seen = <String>{};
|
|
final stack = [of];
|
|
while (stack.isNotEmpty) {
|
|
final x = stack.removeLast();
|
|
if (!seen.add(x)) continue;
|
|
for (final p in parentsOf(x)) {
|
|
if (p == maybeAncestor) return true;
|
|
stack.add(p);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
String toJson() => jsonEncode({
|
|
'people': [for (final p in _people.values) p.toJson()],
|
|
'parent': [
|
|
for (final e in _parents.entries)
|
|
for (final p in e.value) [e.key, p],
|
|
],
|
|
'spouse': [
|
|
for (final e in _spouses.entries)
|
|
for (final s in e.value)
|
|
if (e.key.compareTo(s) < 0) [e.key, s],
|
|
],
|
|
});
|
|
|
|
factory KinGraph.fromJson(String? text) {
|
|
final g = KinGraph();
|
|
if (text == null || text.isEmpty) return g;
|
|
try {
|
|
final j = (jsonDecode(text) as Map).cast<String, Object?>();
|
|
for (final p in (j['people'] as List).cast<Map>()) {
|
|
g.put(KinPerson.fromJson(p.cast<String, Object?>()));
|
|
}
|
|
for (final e in (j['parent'] as List).cast<List>()) {
|
|
g.addParent(e[0] as String, e[1] as String);
|
|
}
|
|
for (final e in (j['spouse'] as List).cast<List>()) {
|
|
g.addSpouse(e[0] as String, e[1] as String);
|
|
}
|
|
} catch (_) {
|
|
return KinGraph(); // 깨진 데이터는 빈 가계도
|
|
}
|
|
return g;
|
|
}
|
|
}
|