ER 다이어그램 설계 연습 문서의 문제 3 풀이 코드다. if __name__ == "__main__": 아래가 자체 테스트다.
"""ER 다이어그램 설계 연습 p3: CONF_REVIEW.
문서: 31.ER 다이어그램 설계 연습 (문제 3)
확인하는 것:
1. 완성 그림의 (min, max): SUBMITS 저자 (1,N)·논문 (1,N), IS_CONTACT 저자 (0,N)·논문 (1,1),
REVIEWS 심사자 (1,N)·논문 (2,4). 요구사항을 지키는 자료는 통과한다.
2. 문서 끝의 물음: Is_contact를 SUBMITS의 속성으로만 두면, 연락 저자가 둘이거나 없는 논문도 통과한다.
IS_CONTACT를 따로 두고 논문 쪽에 (1,1)을 적으면 둘 다 걸린다.
3. 심사자가 1명이거나 5명인 논문, 심사할 논문이 없는 심사자는 REVIEWS에서 걸린다.
4. 그림이 적지 못하는 것: 연락 저자가 그 논문의 저자가 아니어도 (min, max)는 모두 통과한다.
"""
from collections import Counter
N = float("inf")
DESIGN_A = { # 설계 A: Is_contact는 SUBMITS의 속성
"SUBMITS": [("AUTHOR", (1, N)), ("PAPER", (1, N))],
"REVIEWS": [("REVIEWER", (1, N)), ("PAPER", (2, 4))],
}
DESIGN_B = dict(DESIGN_A, IS_CONTACT=[("AUTHOR", (0, N)), ("PAPER", (1, 1))]) # 설계 B: 완성 그림
def violations(schema, 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[rel])
out += [(rel, etype, e, count[e]) for e in entities[etype] if not lo <= count[e] <= hi]
return out
ENTS = {"AUTHOR": ["a1", "a2", "a3"], "REVIEWER": ["r1", "r2", "r3"], "PAPER": ["p1", "p2"]}
SUBMITS = [("a1", "p1"), ("a2", "p1"), ("a2", "p2"), ("a3", "p2")]
REVIEWS = [("r1", "p1"), ("r2", "p1"), ("r1", "p2"), ("r2", "p2"), ("r3", "p2")]
def main():
good = {"SUBMITS": SUBMITS, "REVIEWS": REVIEWS, "IS_CONTACT": [("a1", "p1"), ("a2", "p2")]}
assert violations(DESIGN_B, ENTS, good) == []
print("[OK] 요구사항을 지키는 자료는 완성 그림의 (min, max)를 모두 지킨다")
# 설계 A: Is_contact는 SUBMITS 인스턴스에 붙은 값일 뿐이라 구조 제약이 보지 못한다
flags_two = {("a1", "p1"): True, ("a2", "p1"): True, ("a2", "p2"): True, ("a3", "p2"): False}
flags_none = {k: False for k in flags_two}
contacts = lambda flags, paper: [a for (a, p), f in flags.items() if p == paper and f]
assert len(contacts(flags_two, "p1")) == 2 and len(contacts(flags_none, "p1")) == 0
assert violations(DESIGN_A, ENTS, good) == [] # 표시값이 어떻든 SUBMITS의 짝은 같다
print("[OK] 설계 A: 연락 저자 표시가 둘이어도, 하나도 없어도 구조 제약은 통과한다")
two = dict(good, IS_CONTACT=[("a1", "p1"), ("a2", "p1"), ("a2", "p2")])
none = dict(good, IS_CONTACT=[("a2", "p2")])
assert violations(DESIGN_B, ENTS, two) == [("IS_CONTACT", "PAPER", "p1", 2)]
assert violations(DESIGN_B, ENTS, none) == [("IS_CONTACT", "PAPER", "p1", 0)]
print("[OK] 설계 B: 연락 저자가 둘인 논문, 없는 논문은 IS_CONTACT의 (1,1)에 걸린다")
one_reviewer = dict(good, REVIEWS=[("r1", "p1"), ("r1", "p2"), ("r2", "p2"), ("r3", "p2")])
assert violations(DESIGN_B, ENTS, one_reviewer) == [("REVIEWS", "PAPER", "p1", 1)]
five = dict(ENTS, REVIEWER=["r1", "r2", "r3", "r4", "r5"])
crowd = dict(good, REVIEWS=[(r, "p1") for r in five["REVIEWER"]] + [("r1", "p2"), ("r2", "p2")])
assert violations(DESIGN_B, five, crowd) == [("REVIEWS", "PAPER", "p1", 5)]
idle = dict(ENTS, REVIEWER=["r1", "r2", "r3", "r4"])
assert violations(DESIGN_B, idle, good) == [("REVIEWS", "REVIEWER", "r4", 0)]
print("[OK] 심사자 1명·5명인 논문, 논문이 없는 심사자는 REVIEWS에서 걸린다")
# a3은 p1의 저자가 아닌데 p1의 연락 저자다. (min, max)는 모두 통과한다
outsider = dict(good, IS_CONTACT=[("a3", "p1"), ("a2", "p2")])
assert ("a3", "p1") not in SUBMITS and violations(DESIGN_B, ENTS, outsider) == []
print("[OK] 그림의 한계: 저자가 아닌 사람이 연락 저자여도 (min, max)는 통과한다")
print("ALL CHECKS PASSED")
if __name__ == "__main__":
main()