import 'graph.dart'; import 'titles.dart'; /// 두 사람 사이의 관계. [chon] 은 촌수(배우자는 0), 모르는 사이면 null. class Relation { const Relation(this.kind, this.title, {this.chon, this.up = 0, this.down = 0}); /// self | blood | spouse | inlaw | none final String kind; /// "b 는 a 의 [title]" 에 들어가는 말. final String title; final int? chon; final int up; // 혈족일 때: a 에서 공통 조상까지 올라간 단계 final int down; // 공통 조상에서 b 까지 내려간 단계 bool get related => kind != 'none'; /// "삼촌 (3촌)" 처럼 읽기 좋게. String get label => chon == null || kind == 'self' ? title : (chon == 0 ? title : '$title ($chon촌)'); } class _Anc { const _Anc(this.dist, this.via); final int dist; final String? via; // 처음 올라간 부모 } Map _ancestors(KinGraph g, String id) { final out = {id: const _Anc(0, null)}; var frontier = [id]; var d = 0; while (frontier.isNotEmpty) { d++; final next = []; for (final x in frontier) { for (final p in g.parentsOf(x)) { if (out.containsKey(p)) continue; out[p] = _Anc(d, x == id ? p : out[x]!.via); next.add(p); } } frontier = next; } return out; } /// 혈족 관계의 (올라간 단계, 내려간 단계, a 의 첫 부모, b 의 첫 부모). 이어지지 않으면 null. ({int up, int down, String? aVia, String? bVia})? bloodPath(KinGraph g, String a, String b) { final aa = _ancestors(g, a), bb = _ancestors(g, b); ({int up, int down, String? aVia, String? bVia})? best; for (final id in (aa.keys.toList()..sort())) { final x = bb[id]; if (x == null) continue; final up = aa[id]!.dist, down = x.dist; if (best == null || up + down < best.up + best.down) { best = (up: up, down: down, aVia: aa[id]!.via, bVia: x.via); } } return best; } /// "b 는 a 의 무엇인가". 혈족, 배우자, 배우자 쪽 인척(혈족의 배우자·배우자의 혈족)을 찾는다. Relation relate(KinGraph g, String a, String b) { if (!g.has(a) || !g.has(b)) return const Relation('none', ''); if (a == b) return const Relation('self', '본인', chon: 0); final blood = bloodPath(g, a, b); if (blood != null) { final chon = blood.up + blood.down; return Relation('blood', bloodTitle(g, a, b, blood.up, blood.down, blood.aVia, blood.bVia), chon: chon, up: blood.up, down: blood.down); } if (g.spousesOf(a).contains(b)) return Relation('spouse', spouseTitle(g.person(b)!), chon: 0); // b 가 a 의 혈족(또는 a 본인)의 배우자 Relation? best; for (final s in g.spousesOf(b)) { final r = s == a ? const Relation('self', '본인', chon: 0) : relate0(g, a, s); if (r == null) continue; final t = Relation('inlaw', spouseOfRelativeTitle(g, a, b, s, r), chon: r.chon); if (best == null || (t.chon ?? 99) < (best.chon ?? 99)) best = t; } // b 가 a 의 배우자의 혈족 for (final s in g.spousesOf(a)) { final r = relate0(g, s, b); if (r == null) continue; final t = Relation('inlaw', relativeOfSpouseTitle(g, a, b, s, r), chon: r.chon); if (best == null || (t.chon ?? 99) < (best.chon ?? 99)) best = t; } return best ?? const Relation('none', ''); } /// 혈족만 따진 관계 (배우자를 건너지 않는다). Relation? relate0(KinGraph g, String a, String b) { final p = bloodPath(g, a, b); if (p == null) return null; return Relation('blood', bloodTitle(g, a, b, p.up, p.down, p.aVia, p.bVia), chon: p.up + p.down, up: p.up, down: p.down); }