# ----------------------------------------------------------------------------
# -- HNU CE 데이터베이스(2025년 2학기 02분반): FD 관련 프로그래밍 과제 Part A
# ----------------------------------------------------------------------------
# -- 이름: 이은섭
# -- 학번: 20210508
# ----------------------------------------------------------------------------
import io
from contextlib import redirect_stdout
# 위 두개 import하는 이유:
# (2) 구할때 (1)의 closure() 사용하려고 하는데
# 함수 안에 print문이 출력 안되게 하려고
# (1) 3.2.4절 Algorithm 3.7 관련 closure 함수
# closure : FD 집합 , 속성 집합 -> 속성 집합
# closure(S, {A1,A2...})는 {A1,A2,...}+를 계산
def closure( S, R) :
# 1. split
temp = [ ]
for FD in S:
if len ( FD[ 1 ] ) >= 2 :
for j in range ( len ( FD[ 1 ] ) ) :
temp.append ( ( FD[ 0 ] , [ FD[ 1 ] [ j] ] ) )
S.remove ( FD)
S += temp
# 2. 초기화
X = R.copy ( )
# 3. 반복 확장
temp = [ ]
while ( temp != X) :
temp = X.copy ( )
for FD in S:
if set ( FD[ 0 ] ) .issubset ( set ( X) ) and not ( set ( FD[ 1 ] ) .issubset ( set ( X) ) ) :
X += FD[ 1 ]
X.sort ( )
return X
# (2) 주어진 한 FD가 기존의 FD 집합으로부터 유도 가능한지 검사하는 함수 (closure 활용하면 간단)
# is_derived_from : FD , FD 집합 -> Bool
# is_derived_from(fd, S)는 fd가 S로부터 유도 가능하면 참, 그렇지 않으면 거짓
def is_derived_from( fd, S) :
# 1. 주어진 fd에서 좌항 뽑아내 리스트(R형식으로 만들기)
R = fd[ 0 ]
# print("========fd to List========")
# print(R)
# 2. closure 함수 이용해 좌항에 있는 속성의 클로저 구하기
f = io.StringIO ( ) # closure()안에 있는 print안나오게 하려고 씀
with redirect_stdout( f) :
X = closure( S, R)
# print("========closure calcurate========")
# print(R)
# 3. X값 안에 fd의 우항이 있는지 확인
derived = set ( fd[ 1 ] ) .issubset ( set ( X) )
# 4. 결과 값 반환
return derived
# (3) 3.2.7절 관련 주어진 FD 집합이 기존 FD집합의 basis인지 검사
# is_basis_of :: FD 집합 , FD 집합 -> Bool
# is_basis_of(B, S)는 B가 S의 basis이면 (minimal이 아닌 경우도 포함) 참, 그렇지 않으면 거짓
def is_basis_of( B, S) :
# 1. 두 FD들의 집합 B, S의 좌항에 있는 속성 뽑아 합집합으로 만듬
lefts = [ ]
for FD in B:
if ( FD[ 0 ] not in lefts) :
lefts.append ( FD[ 0 ] )
for FD in S:
if ( FD[ 0 ] not in lefts) :
lefts.append ( FD[ 0 ] )
# print("========Combine leftS and leftB========")
# print(lefts)
# 2. B와 S각각 lefts의 속성들 closure함수 사용하여 비교 둘이 같으면 basis O 틀리면 X
basis = True
for attr in lefts:
f = io.StringIO ( ) # closure()안에 있는 print안나오게 하려고 씀
with redirect_stdout( f) :
if closure( S, attr) != closure( B, attr) :
basis = False
break
# 3. 출력 및 반환
return basis
# ===============================TEST===============================
# (1)-1. closure함수 예제 1 (Example 3.8)
S = [
( [ 'A' , 'B' ] , [ 'C' ] ) ,
( [ 'B' , 'C' ] , [ 'A' , 'D' ] ) ,
( [ 'C' , 'F' ] , [ 'B' ] ) ,
( [ 'D' ] , [ 'E' ] )
]
R = [ 'A' , 'B' ]
print ( "== Example for closure test 1=============================" )
print ( "S = {" , end= '' )
for FD in S:
print ( ' ' .join ( FD[ 0 ] ) , "->" , ' ' .join ( FD[ 1 ] ) , end= ", " if S.index ( FD) != S.index ( S[ -1 ] ) else "" )
print ( "}" )
X = closure( S, R)
print ( "{" , end= '' )
for i in R:
print ( i, end= ',' if R.index ( i) != R.index ( R[ -1 ] ) else "" )
print ( "}+ = " , end= '' )
print ( "{" , end= '' )
for i in X:
print ( i, end= ',' if X.index ( i) != X.index ( X[ -1 ] ) else "" )
print ( "}" )
print ( "" )
# (1)-2. closure함수 예제 2 (MyExample)
R = [ 'C' , 'F' ]
print ( "== Example for closure test 2=============================" )
for FD in S:
print ( ' ' .join ( FD[ 0 ] ) , "->" , ' ' .join ( FD[ 1 ] ) , end= ", " if S.index ( FD) != S.index ( S[ -1 ] ) else "" )
print ( "}" )
X = closure( S, R)
print ( "{" , end= '' )
for i in R:
print ( i, end= ',' if R.index ( i) != R.index ( R[ -1 ] ) else "" )
print ( "}+ = " , end= '' )
print ( "{" , end= '' )
for i in X:
print ( i, end= ',' if X.index ( i) != X.index ( X[ -1 ] ) else "" )
print ( "}" )
print ( "" )
# (2)-1. is_drived_from함수 예제 1 (MyExample)
S = [
( [ 'A' , 'B' ] , [ 'C' ] ) ,
( [ 'B' , 'C' ] , [ 'A' , 'D' ] ) ,
( [ 'C' , 'F' ] , [ 'B' ] ) ,
( [ 'D' ] , [ 'E' ] )
]
fd = ( [ 'A' , 'B' ] , [ 'C' , 'D' ] )
print ( "== Example for is_derived_from test 1=====================" )
print ( "S = {" , end= '' )
for FD in S:
print ( ' ' .join ( FD[ 0 ] ) , "->" , ' ' .join ( FD[ 1 ] ) , end= ", " if S.index ( FD) != S.index ( S[ -1 ] ) else "" )
print ( "}" )
derived = is_derived_from( fd, S)
print ( "{" + ' ' .join ( fd[ 0 ] ) , "->" , ' ' .join ( fd[ 1 ] ) + "}" , " is derived from S :" , derived)
print ( "" )
# (2)-2. is_drived_from함수 예제 2 (MyExample)
S = [
( [ 'A' , 'B' ] , [ 'C' ] ) ,
( [ 'B' , 'C' ] , [ 'A' , 'D' ] ) ,
( [ 'C' , 'F' ] , [ 'B' ] ) ,
( [ 'D' ] , [ 'E' ] )
]
fd = ( [ 'A' , 'B' ] , [ 'C' , 'D' , 'F' ] )
print ( "== Example for is_derived_from test 2=====================" )
print ( "S = {" , end= '' )
for FD in S:
print ( ' ' .join ( FD[ 0 ] ) , "->" , ' ' .join ( FD[ 1 ] ) , end= ", " if S.index ( FD) != S.index ( S[ -1 ] ) else "" )
print ( "}" )
derived = is_derived_from( fd, S)
print ( "{" + ' ' .join ( fd[ 0 ] ) , "->" , ' ' .join ( fd[ 1 ] ) + "}" , " is derived from S :" , derived)
print ( "" )
# (3)-1. is_basis_of 함수 예제 1 (example 3.11)
S = [
( [ 'A' ] , [ 'B' ] ) ,
( [ 'B' ] , [ 'C' ] ) ,
( [ 'C' ] , [ 'A' ] )
]
B = [
( [ 'A' ] , [ 'B' ] ) ,
( [ 'B' ] , [ 'A' ] ) ,
( [ 'B' ] , [ 'C' ] ) ,
( [ 'C' ] , [ 'B' ] )
]
print ( "== Example for is_basis_of test 1=====================" )
print ( "S = {" , end= '' )
for FD in S:
print ( ' ' .join ( FD[ 0 ] ) , "->" , ' ' .join ( FD[ 1 ] ) , end= ", " if S.index ( FD) != S.index ( S[ -1 ] ) else "" )
print ( "}" )
print ( "B = {" , end= '' )
for FD in B:
print ( ' ' .join ( FD[ 0 ] ) , "->" , ' ' .join ( FD[ 1 ] ) , end= ", " if B.index ( FD) != B.index ( B[ -1 ] ) else "" )
print ( "}" )
basis = is_basis_of( B, S)
print ( "B is basis of S :" , basis)
print ( "" )
# (3)-2. is_basis_of 함수 예제 2(MyExample)
S = [
( [ 'A' ] , [ 'B' ] ) ,
( [ 'B' ] , [ 'C' ] ) ,
( [ 'C' ] , [ 'A' ] )
]
B = [
( [ 'A' ] , [ 'B' ] ) ,
( [ 'B' ] , [ 'A' ] ) ,
( [ 'D' ] , [ 'F' ] ) ,
( [ 'C' ] , [ 'B' ] )
]
print ( "== Example for is_basis_of test 2=====================" )
print ( "S = {" , end= '' )
for FD in S:
print ( ' ' .join ( FD[ 0 ] ) , "->" , ' ' .join ( FD[ 1 ] ) , end= ", " if S.index ( FD) != S.index ( S[ -1 ] ) else "" )
print ( "}" )
print ( "B = {" , end= '' )
for FD in B:
print ( ' ' .join ( FD[ 0 ] ) , "->" , ' ' .join ( FD[ 1 ] ) , end= ", " if B.index ( FD) != B.index ( B[ -1 ] ) else "" )
print ( "}" )
basis = is_basis_of( B, S)
print ( "B is basis of S :" , basis)
print ( "" )
IyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgLS0gSE5VIENFIOuNsOydtO2EsOuyoOydtOyKpCgyMDI164WEIDLtlZnquLAgMDLrtoTrsJgpOiBGRCDqtIDroKgg7ZSE66Gc6re4656Y67CNIOqzvOygnCBQYXJ0IEEKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgLS0g7J2066aEOiDsnbTsnYDshK0KIyAtLSDtlZnrsog6IDIwMjEwNTA4CiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKCmltcG9ydCBpbwpmcm9tIGNvbnRleHRsaWIgaW1wb3J0IHJlZGlyZWN0X3N0ZG91dCAKIyDsnIQg65GQ6rCcIGltcG9ydO2VmOuKlCDsnbTsnKA6IAojICgyKSDqtaztlaDrlYwgKDEp7J2YIGNsb3N1cmUoKSDsgqzsmqntlZjroKTqs6Ag7ZWY64qU642wIAojIO2VqOyImCDslYjsl5AgcHJpbnTrrLjsnbQg7Lac66ClIOyViOuQmOqyjCDtlZjroKTqs6AKCiMgKDEpIDMuMi407KCIIEFsZ29yaXRobSAzLjcg6rSA66CoIGNsb3N1cmUg7ZWo7IiYCgojIGNsb3N1cmUgOiBGRCDsp5HtlakgLCDsho3shLEg7KeR7ZWpIC0+IOyGjeyEsSDsp5HtlakKIyBjbG9zdXJlKFMsIHtBMSxBMi4uLn0p64qUIHtBMSxBMiwuLi59K+ulvCDqs4TsgrAKZGVmIGNsb3N1cmUoUywgUik6CiAgICAKCiAgICAjIDEuIHNwbGl0CiAgICB0ZW1wID0gW10KICAgIGZvciBGRCBpbiBTOgogICAgICAgIGlmIGxlbihGRFsxXSkgPj0gMjoKICAgICAgICAgICAgZm9yIGogaW4gcmFuZ2UobGVuKEZEWzFdKSk6CiAgICAgICAgICAgICAgICB0ZW1wLmFwcGVuZCgoRkRbMF0sIFtGRFsxXVtqXV0pKQogICAgICAgICAgICBTLnJlbW92ZShGRCkKICAgIFMgKz0gdGVtcAoKICAgICMgMi4g7LSI6riw7ZmUCiAgICBYID0gUi5jb3B5KCkKICAgIAogICAgIyAzLiDrsJjrs7Ug7ZmV7J6lCiAgICB0ZW1wID0gW10KICAgIHdoaWxlKHRlbXAgIT0gWCk6CiAgICAgICAgdGVtcCA9IFguY29weSgpCiAgICAgICAgZm9yIEZEIGluIFM6CiAgICAgICAgICAgIGlmIHNldChGRFswXSkuaXNzdWJzZXQoc2V0KFgpKSBhbmQgbm90KHNldChGRFsxXSkuaXNzdWJzZXQoc2V0KFgpKSk6CiAgICAgICAgICAgICAgICBYICs9IEZEWzFdCiAgICBYLnNvcnQoKQogICAgCiAgICByZXR1cm4gWAoKCgoKIyAoMikg7KO87Ja07KeEIO2VnCBGROqwgCDquLDsobTsnZggRkQg7KeR7ZWp7Jy866Gc67aA7YSwIOycoOuPhCDqsIDriqXtlZzsp4Ag6rKA7IKs7ZWY64qUIO2VqOyImCAoY2xvc3VyZSDtmZzsmqntlZjrqbQg6rCE64uoKQoKIyBpc19kZXJpdmVkX2Zyb20gOiBGRCAsIEZEIOynke2VqSAtPiBCb29sCiMgaXNfZGVyaXZlZF9mcm9tKGZkLCBTKeuKlCBmZOqwgCBT66Gc67aA7YSwIOycoOuPhCDqsIDriqXtlZjrqbQg7LC4LCDqt7jroIfsp4Ag7JWK7Jy866m0IOqxsOynkwpkZWYgaXNfZGVyaXZlZF9mcm9tKGZkLCBTKToKCgogICAgIyAxLiDso7zslrTsp4QgZmTsl5DshJwg7KKM7ZWtIOu9keyVhOuCtCDrpqzsiqTtirgoUu2YleyLneycvOuhnCDrp4zrk6TquLApCiAgICBSID0gZmRbMF0KICAgICMgcHJpbnQoIj09PT09PT09ZmQgdG8gTGlzdD09PT09PT09IikKICAgICMgcHJpbnQoUikKCiAgICAjIDIuIGNsb3N1cmUg7ZWo7IiYIOydtOyaqe2VtCDsooztla3sl5Ag7J6I64qUIOyGjeyEseydmCDtgbTroZzsoIAg6rWs7ZWY6riwCiAgICBmID0gaW8uU3RyaW5nSU8oKSAjIGNsb3N1cmUoKeyViOyXkCDsnojripQgcHJpbnTslYjrgpjsmKTqsowg7ZWY66Ck6rOgIOyUgAogICAgd2l0aCByZWRpcmVjdF9zdGRvdXQoZik6CiAgICAgICAgWCA9IGNsb3N1cmUoUywgUikKICAgIAogICAgIyBwcmludCgiPT09PT09PT1jbG9zdXJlIGNhbGN1cmF0ZT09PT09PT09IikKICAgICMgcHJpbnQoUikKCiAgICAjIDMuIFjqsJIg7JWI7JeQIGZk7J2YIOyasO2VreydtCDsnojripTsp4Ag7ZmV7J24CiAgICBkZXJpdmVkID0gc2V0KGZkWzFdKS5pc3N1YnNldChzZXQoWCkpCiAgICAKICAgICMgNC4g6rKw6rO8IOqwkiDrsJjtmZgKICAgIHJldHVybiBkZXJpdmVkCgoKCiMgKDMpICAzLjIuN+ygiCDqtIDroKgg7KO87Ja07KeEIEZEIOynke2VqeydtCDquLDsobQgRkTsp5HtlansnZggYmFzaXPsnbjsp4Ag6rKA7IKsCgojIGlzX2Jhc2lzX29mIDo6IEZEIOynke2VqSAsIEZEIOynke2VqSAtPiBCb29sCiMgaXNfYmFzaXNfb2YoQiwgUynripQgQuqwgCBT7J2YIGJhc2lz7J2066m0IChtaW5pbWFs7J20IOyVhOuLjCDqsr3smrDrj4Qg7Y+s7ZWoKSDssLgsIOq3uOugh+yngCDslYrsnLzrqbQg6rGw7KeTCmRlZiBpc19iYXNpc19vZihCLCBTKToKCgogICAgIyAxLiDrkZAgRkTrk6TsnZgg7KeR7ZWpIEIsIFPsnZgg7KKM7ZWt7JeQIOyeiOuKlCDsho3shLEg672R7JWEIO2Vqeynke2VqeycvOuhnCDrp4zrk6wKICAgIGxlZnRzID0gW10KICAgIGZvciBGRCBpbiBCOgogICAgICAgIGlmKEZEWzBdIG5vdCBpbiBsZWZ0cyk6CiAgICAgICAgICAgIGxlZnRzLmFwcGVuZChGRFswXSkKICAgIGZvciBGRCBpbiBTOgogICAgICAgIGlmKEZEWzBdIG5vdCBpbiBsZWZ0cyk6CiAgICAgICAgICAgIGxlZnRzLmFwcGVuZChGRFswXSkKCiAgICAjIHByaW50KCI9PT09PT09PUNvbWJpbmUgbGVmdFMgYW5kIGxlZnRCPT09PT09PT0iKQogICAgIyBwcmludChsZWZ0cykKCiAgICAjIDIuIELsmYAgU+qwgeqwgSBsZWZ0c+ydmCDsho3shLHrk6QgY2xvc3VyZe2VqOyImCDsgqzsmqntlZjsl6wg67mE6rWQIOuRmOydtCDqsJnsnLzrqbQgYmFzaXMgTyDti4DrpqzrqbQgWAogICAgYmFzaXMgPSBUcnVlCiAgICBmb3IgYXR0ciBpbiBsZWZ0czoKICAgICAgICBmID0gaW8uU3RyaW5nSU8oKSAjIGNsb3N1cmUoKeyViOyXkCDsnojripQgcHJpbnTslYjrgpjsmKTqsowg7ZWY66Ck6rOgIOyUgAogICAgICAgIHdpdGggcmVkaXJlY3Rfc3Rkb3V0KGYpOgogICAgICAgICAgICBpZiBjbG9zdXJlKFMsIGF0dHIpICE9IGNsb3N1cmUoQiwgYXR0cik6CiAgICAgICAgICAgICAgICBiYXNpcyA9IEZhbHNlCiAgICAgICAgICAgICAgICBicmVhawogICAgICAgIAogICAgIyAzLiDstpzroKUg67CPIOuwmO2ZmAogICAgcmV0dXJuIGJhc2lzCgogICAgCiAgICAKCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1URVNUPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKIyAoMSktMS4gY2xvc3VyZe2VqOyImCDsmIjsoJwgMSAoRXhhbXBsZSAzLjgpClMgPSBbCiAgICAgKFsnQScsICdCJ10sIFsnQyddKSwgCiAgICAgKFsnQicsICdDJ10sIFsnQScsICdEJ10pLCAKICAgICAoWydDJywgJ0YnXSwgWydCJ10pLCAKICAgICAoWydEJ10sIFsnRSddKQogICAgIF0KUiA9IFsnQScsICdCJ10KcHJpbnQoIj09IEV4YW1wbGUgZm9yIGNsb3N1cmUgdGVzdCAxPT09PT09PT09PT09PT09PT09PT09PT09PT09PT0iKQpwcmludCgiUyA9IHsiLCBlbmQ9JycpCmZvciBGRCBpbiBTOgogICAgcHJpbnQoJyAnLmpvaW4oRkRbMF0pLCAiLT4iLCAgJyAnLmpvaW4oRkRbMV0pLCBlbmQ9IiwgIiBpZiBTLmluZGV4KEZEKSAhPSBTLmluZGV4KFNbLTFdKSBlbHNlICIiKQpwcmludCgifSIpClggPSBjbG9zdXJlKFMsIFIpCgpwcmludCgieyIsIGVuZD0nJykKZm9yIGkgaW4gUjoKICAgIHByaW50KGksIGVuZD0nLCcgaWYgUi5pbmRleChpKSAhPSBSLmluZGV4KFJbLTFdKSBlbHNlICIiKQpwcmludCgifSsgPSAiLCBlbmQ9JycpCgpwcmludCgieyIsIGVuZD0nJykKZm9yIGkgaW4gWDoKICAgIHByaW50KGksIGVuZD0nLCcgaWYgWC5pbmRleChpKSAhPSBYLmluZGV4KFhbLTFdKSBlbHNlICIiKQpwcmludCgifSIpCnByaW50KCIiKQoKIyAoMSktMi4gY2xvc3VyZe2VqOyImCDsmIjsoJwgMiAoTXlFeGFtcGxlKQpSID0gWydDJywgJ0YnXQpwcmludCgiPT0gRXhhbXBsZSBmb3IgY2xvc3VyZSB0ZXN0IDI9PT09PT09PT09PT09PT09PT09PT09PT09PT09PSIpCmZvciBGRCBpbiBTOgogICAgcHJpbnQoJyAnLmpvaW4oRkRbMF0pLCAiLT4iLCAgJyAnLmpvaW4oRkRbMV0pLCBlbmQ9IiwgIiBpZiBTLmluZGV4KEZEKSAhPSBTLmluZGV4KFNbLTFdKSBlbHNlICIiKQpwcmludCgifSIpClggPSBjbG9zdXJlKFMsIFIpCnByaW50KCJ7IiwgZW5kPScnKQpmb3IgaSBpbiBSOgogICAgcHJpbnQoaSwgZW5kPScsJyBpZiBSLmluZGV4KGkpICE9IFIuaW5kZXgoUlstMV0pIGVsc2UgIiIpCnByaW50KCJ9KyA9ICIsIGVuZD0nJykKCnByaW50KCJ7IiwgZW5kPScnKQpmb3IgaSBpbiBYOgogICAgcHJpbnQoaSwgZW5kPScsJyBpZiBYLmluZGV4KGkpICE9IFguaW5kZXgoWFstMV0pIGVsc2UgIiIpCnByaW50KCJ9IikKcHJpbnQoIiIpCgoKIyAoMiktMS4gaXNfZHJpdmVkX2Zyb23tlajsiJgg7JiI7KCcIDEgKE15RXhhbXBsZSkKUyA9IFsKICAgICAoWydBJywgJ0InXSwgWydDJ10pLCAKICAgICAoWydCJywgJ0MnXSwgWydBJywgJ0QnXSksIAogICAgIChbJ0MnLCAnRiddLCBbJ0InXSksIAogICAgIChbJ0QnXSwgWydFJ10pCiAgICAgXQpmZCA9IChbJ0EnLCAnQiddLCBbJ0MnLCAnRCddKQoKcHJpbnQoIj09IEV4YW1wbGUgZm9yIGlzX2Rlcml2ZWRfZnJvbSB0ZXN0IDE9PT09PT09PT09PT09PT09PT09PT0iKQpwcmludCgiUyA9IHsiLCBlbmQ9JycpCmZvciBGRCBpbiBTOgogICAgcHJpbnQoJyAnLmpvaW4oRkRbMF0pLCAiLT4iLCAgJyAnLmpvaW4oRkRbMV0pLCBlbmQ9IiwgIiBpZiBTLmluZGV4KEZEKSAhPSBTLmluZGV4KFNbLTFdKSBlbHNlICIiKQpwcmludCgifSIpCmRlcml2ZWQgPSBpc19kZXJpdmVkX2Zyb20oZmQsIFMpCnByaW50KCJ7IiArICcgJy5qb2luKGZkWzBdKSwgIi0+IiwgJyAnLmpvaW4oZmRbMV0pKyAifSIsICIgaXMgZGVyaXZlZCBmcm9tIFMgOiIsIGRlcml2ZWQpCnByaW50KCIiKQoKIyAoMiktMi4gaXNfZHJpdmVkX2Zyb23tlajsiJgg7JiI7KCcIDIgKE15RXhhbXBsZSkKUyA9IFsKICAgICAoWydBJywgJ0InXSwgWydDJ10pLCAKICAgICAoWydCJywgJ0MnXSwgWydBJywgJ0QnXSksIAogICAgIChbJ0MnLCAnRiddLCBbJ0InXSksIAogICAgIChbJ0QnXSwgWydFJ10pCiAgICAgXQpmZCA9IChbJ0EnLCAnQiddLCBbJ0MnLCAnRCcsICdGJ10pCgpwcmludCgiPT0gRXhhbXBsZSBmb3IgaXNfZGVyaXZlZF9mcm9tIHRlc3QgMj09PT09PT09PT09PT09PT09PT09PSIpCnByaW50KCJTID0geyIsIGVuZD0nJykKZm9yIEZEIGluIFM6CiAgICBwcmludCgnICcuam9pbihGRFswXSksICItPiIsICAnICcuam9pbihGRFsxXSksIGVuZD0iLCAiIGlmIFMuaW5kZXgoRkQpICE9IFMuaW5kZXgoU1stMV0pIGVsc2UgIiIpCnByaW50KCJ9IikKZGVyaXZlZCA9IGlzX2Rlcml2ZWRfZnJvbShmZCwgUykKcHJpbnQoInsiICsgJyAnLmpvaW4oZmRbMF0pLCAiLT4iLCAnICcuam9pbihmZFsxXSkrICJ9IiwgIiBpcyBkZXJpdmVkIGZyb20gUyA6IiwgZGVyaXZlZCkKcHJpbnQoIiIpCgoKIyAoMyktMS4gaXNfYmFzaXNfb2Yg7ZWo7IiYIOyYiOygnCAxIChleGFtcGxlIDMuMTEpClMgPSBbCiAgICAoWydBJ10sIFsnQiddKSwgCiAgICAoWydCJ10sIFsnQyddKSwgCiAgICAoWydDJ10sIFsnQSddKQogICAgXQoKQiA9IFsKICAgIChbJ0EnXSwgWydCJ10pLCAKICAgIChbJ0InXSwgWydBJ10pLCAKICAgIChbJ0InXSwgWydDJ10pLCAKICAgIChbJ0MnXSwgWydCJ10pCiAgICBdCgpwcmludCgiPT0gRXhhbXBsZSBmb3IgaXNfYmFzaXNfb2YgdGVzdCAxPT09PT09PT09PT09PT09PT09PT09IikKcHJpbnQoIlMgPSB7IiwgZW5kPScnKQpmb3IgRkQgaW4gUzoKICAgIHByaW50KCcgJy5qb2luKEZEWzBdKSwgIi0+IiwgICcgJy5qb2luKEZEWzFdKSwgZW5kPSIsICIgaWYgUy5pbmRleChGRCkgIT0gUy5pbmRleChTWy0xXSkgZWxzZSAiIikKcHJpbnQoIn0iKQoKcHJpbnQoIkIgPSB7IiwgZW5kPScnKQpmb3IgRkQgaW4gQjoKICAgIHByaW50KCcgJy5qb2luKEZEWzBdKSwgIi0+IiwgICcgJy5qb2luKEZEWzFdKSwgZW5kPSIsICIgaWYgQi5pbmRleChGRCkgIT0gQi5pbmRleChCWy0xXSkgZWxzZSAiIikKcHJpbnQoIn0iKQpiYXNpcyA9IGlzX2Jhc2lzX29mKEIsIFMpCnByaW50KCJCIGlzIGJhc2lzIG9mIFMgOiIsIGJhc2lzKQpwcmludCgiIikKCiMgKDMpLTIuIGlzX2Jhc2lzX29mIO2VqOyImCDsmIjsoJwgMihNeUV4YW1wbGUpClMgPSBbCiAgICAoWydBJ10sIFsnQiddKSwgCiAgICAoWydCJ10sIFsnQyddKSwgCiAgICAoWydDJ10sIFsnQSddKQogICAgXQoKQiA9IFsKICAgIChbJ0EnXSwgWydCJ10pLCAKICAgIChbJ0InXSwgWydBJ10pLCAKICAgIChbJ0QnXSwgWydGJ10pLCAKICAgIChbJ0MnXSwgWydCJ10pCiAgICBdCgpwcmludCgiPT0gRXhhbXBsZSBmb3IgaXNfYmFzaXNfb2YgdGVzdCAyPT09PT09PT09PT09PT09PT09PT09IikKcHJpbnQoIlMgPSB7IiwgZW5kPScnKQpmb3IgRkQgaW4gUzoKICAgIHByaW50KCcgJy5qb2luKEZEWzBdKSwgIi0+IiwgICcgJy5qb2luKEZEWzFdKSwgZW5kPSIsICIgaWYgUy5pbmRleChGRCkgIT0gUy5pbmRleChTWy0xXSkgZWxzZSAiIikKcHJpbnQoIn0iKQoKcHJpbnQoIkIgPSB7IiwgZW5kPScnKQpmb3IgRkQgaW4gQjoKICAgIHByaW50KCcgJy5qb2luKEZEWzBdKSwgIi0+IiwgICcgJy5qb2luKEZEWzFdKSwgZW5kPSIsICIgaWYgQi5pbmRleChGRCkgIT0gQi5pbmRleChCWy0xXSkgZWxzZSAiIikKcHJpbnQoIn0iKQpiYXNpcyA9IGlzX2Jhc2lzX29mKEIsIFMpCnByaW50KCJCIGlzIGJhc2lzIG9mIFMgOiIsIGJhc2lzKQpwcmludCgiIikK