97 lines
3.5 KiB
Dart
97 lines
3.5 KiB
Dart
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<String, _Anc> _ancestors(KinGraph g, String id) {
|
|
final out = <String, _Anc>{id: const _Anc(0, null)};
|
|
var frontier = <String>[id];
|
|
var d = 0;
|
|
while (frontier.isNotEmpty) {
|
|
d++;
|
|
final next = <String>[];
|
|
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);
|
|
}
|