frindlymap/lib/modules/kinship/src/graph.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;
}
}