ER 모델 연습 문서의 문제 4 풀이 코드다. if __name__ == "__main__": 아래가 자체 테스트다.
"""ER 모델 연습 p4: ER 그림을 보고 문장이 참인지 가리기.
문서: 31.ER 모델 연습 (p4와 변형 문제)
방법: 그림의 숫자와 겹선을 (min, max)로 옮긴다. 문장마다
- True = 그림의 규칙을 지키는 자료라면 늘 참이다(문장을 어기는 자료는 규칙에도 어긋난다).
- False = 그림의 규칙을 지키면서 문장과 반대인 자료가 있다(문장이 "~할 수 없다"고 했는데 할 수 있다).
- Maybe = 규칙을 지키는 자료 가운데 문장이 참인 것도 있고 거짓인 것도 있다.
문서의 답: (a) True (b) Maybe (c) Maybe (d) True (e) False (f) Maybe
"""
from collections import Counter
N = float("inf")
# 문서의 그림을 (min, max)로 옮긴 것. 겹선 = min 1, 홑선 = min 0. 상대 쪽에 적힌 숫자가 이쪽의 max다.
SCHEMA = {
"PERFORMS_IN": [("ACTOR", (1, N)), ("MOVIE", (1, N))], # M:N, 양쪽 겹선
"LEAD_ROLE": [("ACTOR", (0, N)), ("MOVIE", (1, 2))], # ACTOR 옆 2, MOVIE 옆 N, MOVIE 쪽 겹선
"ALSO_A_DIRECTOR": [("ACTOR", (0, 1)), ("DIRECTOR", (0, 1))],
"DIRECTS": [("DIRECTOR", (0, N)), ("MOVIE", (1, 1))], # DIRECTOR 옆 1, MOVIE 옆 N, MOVIE 쪽 겹선
"ACTOR_PRODUCER": [("ACTOR", (0, 1)), ("PRODUCER", (0, 1))],
"PRODUCES": [("PRODUCER", (0, N)), ("MOVIE", (1, N))], # M:N, MOVIE 쪽 겹선
}
def violations(entities, rels):
out = []
for rel, roles in SCHEMA.items():
for i, (etype, (lo, hi)) in enumerate(roles):
count = Counter(t[i] for t in rels.get(rel, []))
out += [(rel, etype, e, count[e]) for e in entities[etype] if not lo <= count[e] <= hi]
return out
def valid(entities, rels):
return not violations(entities, rels)
def base(n_movies=1):
"""규칙을 모두 지키는 가장 작은 자료: 배우 a1, 감독 d1, 제작자 p1, 영화 n편."""
movies = [f"m{k}" for k in range(1, n_movies + 1)]
ents = {"ACTOR": ["a1"], "DIRECTOR": ["d1"], "PRODUCER": ["p1"], "MOVIE": movies}
rels = {"PERFORMS_IN": [("a1", m) for m in movies], "LEAD_ROLE": [("a1", m) for m in movies],
"DIRECTS": [("d1", m) for m in movies], "PRODUCES": [("p1", m) for m in movies],
"ALSO_A_DIRECTOR": [], "ACTOR_PRODUCER": []}
return ents, rels
def main():
ents, rels = base()
assert valid(ents, rels)
# (a) 영화에 한 편도 안 나온 배우는 없다 -> True
lonely = dict(ents, ACTOR=["a1", "a2"])
assert violations(lonely, rels) == [("PERFORMS_IN", "ACTOR", "a2", 0)]
assert SCHEMA["PERFORMS_IN"][0][1][0] >= 1
print("[OK] (a) True: 영화에 안 나온 배우 a2를 넣으면 PERFORMS_IN의 전체 참여에 어긋난다")
# (b) 열 편 넘게 출연한 배우가 있다 -> Maybe
e11, r11 = base(11)
assert valid(e11, r11) and Counter(a for a, _ in r11["PERFORMS_IN"])["a1"] == 11
assert valid(ents, rels) and max(Counter(a for a, _ in rels["PERFORMS_IN"]).values()) <= 10
print("[OK] (b) Maybe: 11편에 나온 배우가 있는 자료도, 없는 자료도 규칙을 지킨다")
# (c) 여러 영화에서 주연을 한 배우가 있다 -> Maybe
e2, r2 = base(2)
assert valid(e2, r2) and Counter(a for a, _ in r2["LEAD_ROLE"])["a1"] == 2
assert max(Counter(a for a, _ in rels["LEAD_ROLE"]).values()) == 1
print("[OK] (c) Maybe: 한 배우가 두 영화의 주연인 자료도, 아닌 자료도 규칙을 지킨다")
# (d) 한 영화의 주연은 많아야 두 명 -> True
three = dict(ents, ACTOR=["a1", "a2", "a3"])
r3 = dict(rels, PERFORMS_IN=[(a, "m1") for a in three["ACTOR"]], LEAD_ROLE=[(a, "m1") for a in three["ACTOR"]])
assert violations(three, r3) == [("LEAD_ROLE", "MOVIE", "m1", 3)]
two = dict(ents, ACTOR=["a1", "a2"])
r2lead = dict(rels, PERFORMS_IN=[("a1", "m1"), ("a2", "m1")], LEAD_ROLE=[("a1", "m1"), ("a2", "m1")])
assert valid(two, r2lead) and SCHEMA["LEAD_ROLE"][1][1][1] == 2
print("[OK] (d) True: 주연 둘은 통과하고 셋은 LEAD_ROLE의 위 한계 2에 걸린다")
# (e) 제작자는 다른 영화의 배우가 될 수 없다 -> False
em, rm = base(2)
em = dict(em, PRODUCER=["p1", "p2"])
rm = dict(rm, ACTOR_PRODUCER=[("a1", "p1")], PRODUCES=[("p1", "m1"), ("p2", "m2")])
# 제작자 p1은 배우 a1과 같은 사람이다(ACTOR_PRODUCER). p1은 m1만 제작했고, a1은 다른 영화 m2에도 나온다
assert valid(em, rm) and ("a1", "m2") in rm["PERFORMS_IN"] and ("p1", "m2") not in rm["PRODUCES"]
print("[OK] (e) False: m1의 제작자가 다른 영화 m2의 배우인 자료가 규칙을 지킨다")
# (f) 감독은 한 명인데 제작자는 여럿인 영화가 있다 -> Maybe
several = dict(ents, PRODUCER=["p1", "p2"])
rs = dict(rels, PRODUCES=[("p1", "m1"), ("p2", "m1")])
assert valid(several, rs) and Counter(m for _, m in rs["DIRECTS"])["m1"] == 1
assert valid(ents, rels) and Counter(m for _, m in rels["PRODUCES"])["m1"] == 1
two_directors = dict(ents, DIRECTOR=["d1", "d2"])
rd = dict(rels, DIRECTS=[("d1", "m1"), ("d2", "m1")])
assert violations(two_directors, rd) == [("DIRECTS", "MOVIE", "m1", 2)]
print("[OK] (f) Maybe: 제작자가 둘인 영화가 있는 자료도, 없는 자료도 규칙을 지킨다. 감독 둘은 규칙에 어긋난다")
# 변형 문제 (g) 감독이 없는 영화가 있을 수 있다 -> False, (h) 배우이면서 감독인 사람이 있다 -> Maybe
assert violations(ents, dict(rels, DIRECTS=[])) == [("DIRECTS", "MOVIE", "m1", 0)]
assert valid(ents, dict(rels, ALSO_A_DIRECTOR=[("a1", "d1")])) and valid(ents, rels)
print("[OK] 변형: (g) 감독 없는 영화는 규칙에 어긋난다, (h) 배우 겸 감독은 있어도 없어도 된다")
print("ALL CHECKS PASSED")
if __name__ == "__main__":
main()