# ----------------------------------------------------------------------------
# -- HNU CE 데이터베이스(2025년 2학기 02분반): FD 관련 프로그래밍 과제 Part B
# ----------------------------------------------------------------------------
# -- 이름: 이은섭
# -- 학번: 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
# (4) 3.2.7절 관련 주어진 FD 집합이 minimal인지 검사
# is_minimal :: [FD] -> Bool
# is_minimal(B)는 B가 3.2.7절에 제시된 기준에 따라 minimal하면 참, 그렇지 않으면 거짓
def is_minimal( B) :
answer = True
# 1. 모든 FD의 우항이 singleton인지 검사
for FD in B:
if len ( FD[ 1 ] ) != 1 : # 2개 이상일 경우 minimal 아님
answer = False
return answer
# 2. FD 하나라도 제거하면 동등성 깨지는지 검사
for FD in B:
temp = B.copy ( )
temp.remove ( FD)
if is_basis_of( B, temp) : # 제거해도 동등하면 minimal 아님
answer = False
return answer
# 3. FD의 좌항에서 속성을 줄이면 동등성 깨지는지 검사
for FD in B:
left = FD[ 0 ]
right = FD[ 1 ]
if len ( left) >= 2 :
for attr in left:
reduced_left = left.copy ( )
reduced_left.remove ( attr)
temp = B.copy ( )
temp.remove ( FD)
temp.append ( ( reduced_left, right) )
if is_basis_of( B, temp) : # 왼쪽 속성 줄여도 동등하면 minimal 아님
answer = False
return answer
return answer
# (5) 3.2.8절 Algorithm 3.12
# project_FDs :: [Attr] -> [FD] -> [Attr] -> [FD]
# project_FDs(L, S, L1)은
# 원래 관계(R)의 속성 집합을 L과 FD집합을 S라 할 때
# L의 부분집합인 L1을 속성으로 하는 (즉, R의 프로젝션인 R1) 관계에서 성립하는 성질을 나타내는 FD집합(즉, S의 프로젝션인 S1)을 계산
def project_FDs( L, S, L1) :
T = [ ] # projection 결과 잠정 FD 집합
# 1. L1의 각 속성에 대해 closure 계산 → L1 내부에서 유효한 FD 추가
for attr in L1:
X = closure( S, [ attr] )
for i in X:
if i not in [ attr] and i in L1: # 자기 자신 제외, L1 내부 속성만
T.append ( ( [ attr] , [ i] ) )
# 2. 중복/비최소 FD 제거 (minimal form으로 정리)
i = 0
while i < len ( T) :
temp = T.copy ( )
FD = T[ i]
temp.remove ( FD)
if is_basis_of( T, temp) : # 제거해도 동등하면 삭제
T.remove ( FD)
i = 0 # 다시 처음부터 확인
else :
i += 1
return T
# ===============================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 ( "" )
# (4)-1 is_minimal 함수 예제1 (example 3.11)
print ( "== Example for is_minimal test 1===========================" )
B = [
( [ 'A' ] , [ 'B' ] ) ,
( [ 'B' ] , [ 'A' ] ) ,
( [ 'B' ] , [ 'C' ] ) ,
( [ 'C' ] , [ 'B' ] )
]
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 ( "}" )
minimal = is_minimal( B)
print ( "B is minimal :" , minimal)
print ( "" )
# (4)-2 is_minimal 함수 예제2 (my example)
print ( "== Example for is_minimal test 2===========================" )
B = [
( [ 'A' ] , [ 'B' ] ) ,
( [ 'B' ] , [ 'C' ] ) ,
( [ 'A' ] , [ 'C' ] )
]
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 ( "}" )
minimal = is_minimal( B)
print ( "B is minimal :" , minimal)
print ( "" )
# (5)-1. project_FDs 함수 예제 (example 3.13)
L = [ 'A' , 'B' , 'C' , 'D' ]
S = [
( [ 'A' ] , [ 'B' ] ) ,
( [ 'B' ] , [ 'C' ] ) ,
( [ 'C' ] , [ 'D' ] )
]
L1 = [ 'A' , 'C' , 'D' ]
print ( "== Example for project_FDs test 1===========================" )
print ( "L = {" , end= '' )
for FD in L:
print ( FD, end= "," if L.index ( FD) != L.index ( L[ -1 ] ) else "" )
print ( "}" )
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 ( "L1 = {" , end= '' )
for FD in L1:
print ( FD, end= "," if L1.index ( FD) != L1.index ( L1[ -1 ] ) else "" )
print ( "}" )
S1 = project_FDs( L, S, L1)
print ( "S1 = {" , end= '' )
for FD in S1:
print ( ' ' .join ( FD[ 0 ] ) , "->" , ' ' .join ( FD[ 1 ] ) , end= ", " if S1.index ( FD) != S1.index ( S1[ -1 ] ) else "" )
print ( "}" )
print ( "" )
# (5)-2. project_FDs 함수 예제 (my example)
L = [ 'A' , 'B' , 'C' , 'D' , 'E' ]
S = [
( [ 'A' ] , [ 'B' ] ) ,
( [ 'A' ] , [ 'C' ] ) ,
( [ 'B' ] , [ 'C' ] ) ,
( [ 'C' ] , [ 'D' ] ) ,
( [ 'D' ] , [ 'E' ] )
]
L1 = [ 'A' , 'B' , 'D' ]
print ( "== Example for project_FDs test 2===========================" )
print ( "L = {" , end= '' )
for FD in L:
print ( FD, end= "," if L.index ( FD) != L.index ( L[ -1 ] ) else "" )
print ( "}" )
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 ( "L1 = {" , end= '' )
for FD in L1:
print ( FD, end= "," if L1.index ( FD) != L1.index ( L1[ -1 ] ) else "" )
print ( "}" )
S1 = project_FDs( L, S, L1)
print ( "S1 = {" , end= '' )
for FD in S1:
print ( ' ' .join ( FD[ 0 ] ) , "->" , ' ' .join ( FD[ 1 ] ) , end= ", " if S1.index ( FD) != S1.index ( S1[ -1 ] ) else "" )
print ( "}" )
print ( "" )
IyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgLS0gSE5VIENFIOuNsOydtO2EsOuyoOydtOyKpCgyMDI164WEIDLtlZnquLAgMDLrtoTrsJgpOiBGRCDqtIDroKgg7ZSE66Gc6re4656Y67CNIOqzvOygnCBQYXJ0IEIKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgLS0g7J2066aEOiDsnbTsnYDshK0KIyAtLSDtlZnrsog6IDIwMjEwNTA4CiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKCmltcG9ydCBpbwpmcm9tIGNvbnRleHRsaWIgaW1wb3J0IHJlZGlyZWN0X3N0ZG91dCAKIyDsnIQg65GQ6rCcIGltcG9ydO2VmOuKlCDsnbTsnKA6IAojICgyKSDqtaztlaDrlYwgKDEp7J2YIGNsb3N1cmUoKSDsgqzsmqntlZjroKTqs6Ag7ZWY64qU642wIAojIO2VqOyImCDslYjsl5AgcHJpbnTrrLjsnbQg7Lac66ClIOyViOuQmOqyjCDtlZjroKTqs6AKCiMgKDEpIDMuMi407KCIIEFsZ29yaXRobSAzLjcg6rSA66CoIGNsb3N1cmUg7ZWo7IiYCgojIGNsb3N1cmUgOiBGRCDsp5HtlakgLCDsho3shLEg7KeR7ZWpIC0+IOyGjeyEsSDsp5HtlakKIyBjbG9zdXJlKFMsIHtBMSxBMi4uLn0p64qUIHtBMSxBMiwuLi59K+ulvCDqs4TsgrAKZGVmIGNsb3N1cmUoUywgUik6CiAgICAKCiAgICAjIDEuIHNwbGl0CiAgICB0ZW1wID0gW10KICAgIGZvciBGRCBpbiBTOgogICAgICAgIGlmIGxlbihGRFsxXSkgPj0gMjoKICAgICAgICAgICAgZm9yIGogaW4gcmFuZ2UobGVuKEZEWzFdKSk6CiAgICAgICAgICAgICAgICB0ZW1wLmFwcGVuZCgoRkRbMF0sIFtGRFsxXVtqXV0pKQogICAgICAgICAgICBTLnJlbW92ZShGRCkKICAgIFMgKz0gdGVtcAoKICAgICMgMi4g7LSI6riw7ZmUCiAgICBYID0gUi5jb3B5KCkKICAgIAogICAgIyAzLiDrsJjrs7Ug7ZmV7J6lCiAgICB0ZW1wID0gW10KICAgIHdoaWxlKHRlbXAgIT0gWCk6CiAgICAgICAgdGVtcCA9IFguY29weSgpCiAgICAgICAgZm9yIEZEIGluIFM6CiAgICAgICAgICAgIGlmIHNldChGRFswXSkuaXNzdWJzZXQoc2V0KFgpKSBhbmQgbm90KHNldChGRFsxXSkuaXNzdWJzZXQoc2V0KFgpKSk6CiAgICAgICAgICAgICAgICBYICs9IEZEWzFdCiAgICBYLnNvcnQoKQogICAgCiAgICByZXR1cm4gWAoKCgoKIyAoMikg7KO87Ja07KeEIO2VnCBGROqwgCDquLDsobTsnZggRkQg7KeR7ZWp7Jy866Gc67aA7YSwIOycoOuPhCDqsIDriqXtlZzsp4Ag6rKA7IKs7ZWY64qUIO2VqOyImCAoY2xvc3VyZSDtmZzsmqntlZjrqbQg6rCE64uoKQoKIyBpc19kZXJpdmVkX2Zyb20gOiBGRCAsIEZEIOynke2VqSAtPiBCb29sCiMgaXNfZGVyaXZlZF9mcm9tKGZkLCBTKeuKlCBmZOqwgCBT66Gc67aA7YSwIOycoOuPhCDqsIDriqXtlZjrqbQg7LC4LCDqt7jroIfsp4Ag7JWK7Jy866m0IOqxsOynkwpkZWYgaXNfZGVyaXZlZF9mcm9tKGZkLCBTKToKCgogICAgIyAxLiDso7zslrTsp4QgZmTsl5DshJwg7KKM7ZWtIOu9keyVhOuCtCDrpqzsiqTtirgoUu2YleyLneycvOuhnCDrp4zrk6TquLApCiAgICBSID0gZmRbMF0KICAgICMgcHJpbnQoIj09PT09PT09ZmQgdG8gTGlzdD09PT09PT09IikKICAgICMgcHJpbnQoUikKCiAgICAjIDIuIGNsb3N1cmUg7ZWo7IiYIOydtOyaqe2VtCDsooztla3sl5Ag7J6I64qUIOyGjeyEseydmCDtgbTroZzsoIAg6rWs7ZWY6riwCiAgICBmID0gaW8uU3RyaW5nSU8oKSAjIGNsb3N1cmUoKeyViOyXkCDsnojripQgcHJpbnTslYjrgpjsmKTqsowg7ZWY66Ck6rOgIOyUgAogICAgd2l0aCByZWRpcmVjdF9zdGRvdXQoZik6CiAgICAgICAgWCA9IGNsb3N1cmUoUywgUikKICAgIAogICAgIyBwcmludCgiPT09PT09PT1jbG9zdXJlIGNhbGN1cmF0ZT09PT09PT09IikKICAgICMgcHJpbnQoUikKCiAgICAjIDMuIFjqsJIg7JWI7JeQIGZk7J2YIOyasO2VreydtCDsnojripTsp4Ag7ZmV7J24CiAgICBkZXJpdmVkID0gc2V0KGZkWzFdKS5pc3N1YnNldChzZXQoWCkpCiAgICAKICAgICMgNC4g6rKw6rO8IOqwkiDrsJjtmZgKICAgIHJldHVybiBkZXJpdmVkCgoKCiMgKDMpICAzLjIuN+ygiCDqtIDroKgg7KO87Ja07KeEIEZEIOynke2VqeydtCDquLDsobQgRkTsp5HtlansnZggYmFzaXPsnbjsp4Ag6rKA7IKsCgojIGlzX2Jhc2lzX29mIDo6IEZEIOynke2VqSAsIEZEIOynke2VqSAtPiBCb29sCiMgaXNfYmFzaXNfb2YoQiwgUynripQgQuqwgCBT7J2YIGJhc2lz7J2066m0IChtaW5pbWFs7J20IOyVhOuLjCDqsr3smrDrj4Qg7Y+s7ZWoKSDssLgsIOq3uOugh+yngCDslYrsnLzrqbQg6rGw7KeTCmRlZiBpc19iYXNpc19vZihCLCBTKToKCgogICAgIyAxLiDrkZAgRkTrk6TsnZgg7KeR7ZWpIEIsIFPsnZgg7KKM7ZWt7JeQIOyeiOuKlCDsho3shLEg672R7JWEIO2Vqeynke2VqeycvOuhnCDrp4zrk6wKICAgIGxlZnRzID0gW10KICAgIGZvciBGRCBpbiBCOgogICAgICAgIGlmKEZEWzBdIG5vdCBpbiBsZWZ0cyk6CiAgICAgICAgICAgIGxlZnRzLmFwcGVuZChGRFswXSkKICAgIGZvciBGRCBpbiBTOgogICAgICAgIGlmKEZEWzBdIG5vdCBpbiBsZWZ0cyk6CiAgICAgICAgICAgIGxlZnRzLmFwcGVuZChGRFswXSkKCiAgICAjIHByaW50KCI9PT09PT09PUNvbWJpbmUgbGVmdFMgYW5kIGxlZnRCPT09PT09PT0iKQogICAgIyBwcmludChsZWZ0cykKCiAgICAjIDIuIELsmYAgU+qwgeqwgSBsZWZ0c+ydmCDsho3shLHrk6QgY2xvc3VyZe2VqOyImCDsgqzsmqntlZjsl6wg67mE6rWQIOuRmOydtCDqsJnsnLzrqbQgYmFzaXMgTyDti4DrpqzrqbQgWAogICAgYmFzaXMgPSBUcnVlCiAgICBmb3IgYXR0ciBpbiBsZWZ0czoKICAgICAgICBmID0gaW8uU3RyaW5nSU8oKSAjIGNsb3N1cmUoKeyViOyXkCDsnojripQgcHJpbnTslYjrgpjsmKTqsowg7ZWY66Ck6rOgIOyUgAogICAgICAgIHdpdGggcmVkaXJlY3Rfc3Rkb3V0KGYpOgogICAgICAgICAgICBpZiBjbG9zdXJlKFMsIGF0dHIpICE9IGNsb3N1cmUoQiwgYXR0cik6CiAgICAgICAgICAgICAgICBiYXNpcyA9IEZhbHNlCiAgICAgICAgICAgICAgICBicmVhawogICAgICAgIAogICAgIyAzLiDstpzroKUg67CPIOuwmO2ZmAogICAgcmV0dXJuIGJhc2lzCgojICg0KSAgMy4yLjfsoIgg6rSA66CoIOyjvOyWtOynhCBGRCDsp5HtlansnbQgbWluaW1hbOyduOyngCDqsoDsgqwKIyBpc19taW5pbWFsIDo6IFtGRF0gLT4gQm9vbCAgICAgICAgICAgICAKIyBpc19taW5pbWFsKEIp64qUIELqsIAgMy4yLjfsoIjsl5Ag7KCc7Iuc65CcIOq4sOykgOyXkCDrlLDrnbwgbWluaW1hbO2VmOuptCDssLgsIOq3uOugh+yngCDslYrsnLzrqbQg6rGw7KeTCgpkZWYgaXNfbWluaW1hbChCKToKICAgIGFuc3dlciA9IFRydWUKCiAgICAjIDEuIOuqqOuToCBGROydmCDsmrDtla3snbQgc2luZ2xldG9u7J247KeAIOqygOyCrAogICAgZm9yIEZEIGluIEI6CiAgICAgICAgaWYgbGVuKEZEWzFdKSAhPSAxOiAgICMgMuqwnCDsnbTsg4Hsnbwg6rK97JqwIG1pbmltYWwg7JWE64uYCiAgICAgICAgICAgIGFuc3dlciA9IEZhbHNlCiAgICAgICAgICAgIHJldHVybiBhbnN3ZXIKCiAgICAjIDIuIEZEIO2VmOuCmOudvOuPhCDsoJzqsbDtlZjrqbQg64+Z65Ox7ISxIOq5qOyngOuKlOyngCDqsoDsgqwKICAgIGZvciBGRCBpbiBCOgogICAgICAgIHRlbXAgPSBCLmNvcHkoKQogICAgICAgIHRlbXAucmVtb3ZlKEZEKQogICAgICAgIGlmIGlzX2Jhc2lzX29mKEIsIHRlbXApOiAgIyDsoJzqsbDtlbTrj4Qg64+Z65Ox7ZWY66m0IG1pbmltYWwg7JWE64uYCiAgICAgICAgICAgIGFuc3dlciA9IEZhbHNlCiAgICAgICAgICAgIHJldHVybiBhbnN3ZXIKCiAgICAjIDMuIEZE7J2YIOyijO2VreyXkOyEnCDsho3shLHsnYQg7KSE7J2066m0IOuPmeuTseyEsSDquajsp4DripTsp4Ag6rKA7IKsCiAgICBmb3IgRkQgaW4gQjoKICAgICAgICBsZWZ0ID0gRkRbMF0KICAgICAgICByaWdodCA9IEZEWzFdCiAgICAgICAgaWYgbGVuKGxlZnQpID49IDI6CiAgICAgICAgICAgIGZvciBhdHRyIGluIGxlZnQ6CiAgICAgICAgICAgICAgICByZWR1Y2VkX2xlZnQgPSBsZWZ0LmNvcHkoKQogICAgICAgICAgICAgICAgcmVkdWNlZF9sZWZ0LnJlbW92ZShhdHRyKQogICAgICAgICAgICAgICAgdGVtcCA9IEIuY29weSgpCiAgICAgICAgICAgICAgICB0ZW1wLnJlbW92ZShGRCkKICAgICAgICAgICAgICAgIHRlbXAuYXBwZW5kKChyZWR1Y2VkX2xlZnQsIHJpZ2h0KSkKICAgICAgICAgICAgICAgIGlmIGlzX2Jhc2lzX29mKEIsIHRlbXApOiAgIyDsmbzsqr0g7IaN7ISxIOykhOyXrOuPhCDrj5nrk7HtlZjrqbQgbWluaW1hbCDslYTri5gKICAgICAgICAgICAgICAgICAgICBhbnN3ZXIgPSBGYWxzZQogICAgICAgICAgICAgICAgICAgIHJldHVybiBhbnN3ZXIKCiAgICByZXR1cm4gYW5zd2VyCgojICg1KSAzLjIuOOygiCBBbGdvcml0aG0gMy4xMgojIHByb2plY3RfRkRzIDo6IFtBdHRyXSAtPiBbRkRdIC0+IFtBdHRyXSAtPiBbRkRdCiMgcHJvamVjdF9GRHMoTCwgUywgTDEp7J2ACiMgICDsm5Drnpgg6rSA6rOEKFIp7J2YIOyGjeyEsSDsp5HtlansnYQgTOqzvCBGROynke2VqeydhCBT6528IO2VoCDrlYwKIyAgIEzsnZgg67aA67aE7KeR7ZWp7J24IEwx7J2EIOyGjeyEseycvOuhnCDtlZjripQgKOymiSwgUuydmCDtlITroZzsoJ3shZjsnbggUjEpIOq0gOqzhOyXkOyEnCDshLHrpr3tlZjripQg7ISx7KeI7J2EIOuCmO2DgOuCtOuKlCBGROynke2VqSjspoksIFPsnZgg7ZSE66Gc7KCd7IWY7J24IFMxKeydhCDqs4TsgrAKZGVmIHByb2plY3RfRkRzKEwsIFMsIEwxKToKICAgIFQgPSBbXSAgICMgcHJvamVjdGlvbiDqsrDqs7wg7J6g7KCVIEZEIOynke2VqQoKICAgICMgMS4gTDHsnZgg6rCBIOyGjeyEseyXkCDrjIDtlbQgY2xvc3VyZSDqs4TsgrAg4oaSIEwxIOuCtOu2gOyXkOyEnCDsnKDtmqjtlZwgRkQg7LaU6rCACiAgICBmb3IgYXR0ciBpbiBMMToKICAgICAgICBYID0gY2xvc3VyZShTLCBbYXR0cl0pICAKICAgICAgICBmb3IgaSBpbiBYOgogICAgICAgICAgICBpZiBpIG5vdCBpbiBbYXR0cl0gYW5kIGkgaW4gTDE6ICAjIOyekOq4sCDsnpDsi6Ag7KCc7Jm4LCBMMSDrgrTrtoAg7IaN7ISx66eMCiAgICAgICAgICAgICAgICBULmFwcGVuZCgoW2F0dHJdLCBbaV0pKQoKICAgICMgMi4g7KSR67O1L+u5hOy1nOyGjCBGRCDsoJzqsbAgKG1pbmltYWwgZm9ybeycvOuhnCDsoJXrpqwpCiAgICBpID0gMAogICAgd2hpbGUgaSA8IGxlbihUKToKICAgICAgICB0ZW1wID0gVC5jb3B5KCkKICAgICAgICBGRCA9IFRbaV0KICAgICAgICB0ZW1wLnJlbW92ZShGRCkKICAgICAgICBpZiBpc19iYXNpc19vZihULCB0ZW1wKTogICMg7KCc6rGw7ZW064+EIOuPmeuTse2VmOuptCDsgq3soJwKICAgICAgICAgICAgVC5yZW1vdmUoRkQpCiAgICAgICAgICAgIGkgPSAwICAjIOuLpOyLnCDsspjsnYzrtoDthLAg7ZmV7J24CiAgICAgICAgZWxzZToKICAgICAgICAgICAgaSArPSAxCgogICAgcmV0dXJuIFQKCgoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09VEVTVD09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiMgKDEpLTEuIGNsb3N1cmXtlajsiJgg7JiI7KCcIDEgKEV4YW1wbGUgMy44KQpTID0gWwogICAgIChbJ0EnLCAnQiddLCBbJ0MnXSksIAogICAgIChbJ0InLCAnQyddLCBbJ0EnLCAnRCddKSwgCiAgICAgKFsnQycsICdGJ10sIFsnQiddKSwgCiAgICAgKFsnRCddLCBbJ0UnXSkKICAgICBdClIgPSBbJ0EnLCAnQiddCnByaW50KCI9PSBFeGFtcGxlIGZvciBjbG9zdXJlIHRlc3QgMT09PT09PT09PT09PT09PT09PT09PT09PT09PT09IikKcHJpbnQoIlMgPSB7IiwgZW5kPScnKQpmb3IgRkQgaW4gUzoKICAgIHByaW50KCcgJy5qb2luKEZEWzBdKSwgIi0+IiwgICcgJy5qb2luKEZEWzFdKSwgZW5kPSIsICIgaWYgUy5pbmRleChGRCkgIT0gUy5pbmRleChTWy0xXSkgZWxzZSAiIikKcHJpbnQoIn0iKQpYID0gY2xvc3VyZShTLCBSKQoKcHJpbnQoInsiLCBlbmQ9JycpCmZvciBpIGluIFI6CiAgICBwcmludChpLCBlbmQ9JywnIGlmIFIuaW5kZXgoaSkgIT0gUi5pbmRleChSWy0xXSkgZWxzZSAiIikKcHJpbnQoIn0rID0gIiwgZW5kPScnKQoKcHJpbnQoInsiLCBlbmQ9JycpCmZvciBpIGluIFg6CiAgICBwcmludChpLCBlbmQ9JywnIGlmIFguaW5kZXgoaSkgIT0gWC5pbmRleChYWy0xXSkgZWxzZSAiIikKcHJpbnQoIn0iKQpwcmludCgiIikKCiMgKDEpLTIuIGNsb3N1cmXtlajsiJgg7JiI7KCcIDIgKE15RXhhbXBsZSkKUiA9IFsnQycsICdGJ10KcHJpbnQoIj09IEV4YW1wbGUgZm9yIGNsb3N1cmUgdGVzdCAyPT09PT09PT09PT09PT09PT09PT09PT09PT09PT0iKQpmb3IgRkQgaW4gUzoKICAgIHByaW50KCcgJy5qb2luKEZEWzBdKSwgIi0+IiwgICcgJy5qb2luKEZEWzFdKSwgZW5kPSIsICIgaWYgUy5pbmRleChGRCkgIT0gUy5pbmRleChTWy0xXSkgZWxzZSAiIikKcHJpbnQoIn0iKQpYID0gY2xvc3VyZShTLCBSKQpwcmludCgieyIsIGVuZD0nJykKZm9yIGkgaW4gUjoKICAgIHByaW50KGksIGVuZD0nLCcgaWYgUi5pbmRleChpKSAhPSBSLmluZGV4KFJbLTFdKSBlbHNlICIiKQpwcmludCgifSsgPSAiLCBlbmQ9JycpCgpwcmludCgieyIsIGVuZD0nJykKZm9yIGkgaW4gWDoKICAgIHByaW50KGksIGVuZD0nLCcgaWYgWC5pbmRleChpKSAhPSBYLmluZGV4KFhbLTFdKSBlbHNlICIiKQpwcmludCgifSIpCnByaW50KCIiKQoKCiMgKDIpLTEuIGlzX2RyaXZlZF9mcm9t7ZWo7IiYIOyYiOygnCAxIChNeUV4YW1wbGUpClMgPSBbCiAgICAgKFsnQScsICdCJ10sIFsnQyddKSwgCiAgICAgKFsnQicsICdDJ10sIFsnQScsICdEJ10pLCAKICAgICAoWydDJywgJ0YnXSwgWydCJ10pLCAKICAgICAoWydEJ10sIFsnRSddKQogICAgIF0KZmQgPSAoWydBJywgJ0InXSwgWydDJywgJ0QnXSkKCnByaW50KCI9PSBFeGFtcGxlIGZvciBpc19kZXJpdmVkX2Zyb20gdGVzdCAxPT09PT09PT09PT09PT09PT09PT09IikKcHJpbnQoIlMgPSB7IiwgZW5kPScnKQpmb3IgRkQgaW4gUzoKICAgIHByaW50KCcgJy5qb2luKEZEWzBdKSwgIi0+IiwgICcgJy5qb2luKEZEWzFdKSwgZW5kPSIsICIgaWYgUy5pbmRleChGRCkgIT0gUy5pbmRleChTWy0xXSkgZWxzZSAiIikKcHJpbnQoIn0iKQpkZXJpdmVkID0gaXNfZGVyaXZlZF9mcm9tKGZkLCBTKQpwcmludCgieyIgKyAnICcuam9pbihmZFswXSksICItPiIsICcgJy5qb2luKGZkWzFdKSsgIn0iLCAiIGlzIGRlcml2ZWQgZnJvbSBTIDoiLCBkZXJpdmVkKQpwcmludCgiIikKCiMgKDIpLTIuIGlzX2RyaXZlZF9mcm9t7ZWo7IiYIOyYiOygnCAyIChNeUV4YW1wbGUpClMgPSBbCiAgICAgKFsnQScsICdCJ10sIFsnQyddKSwgCiAgICAgKFsnQicsICdDJ10sIFsnQScsICdEJ10pLCAKICAgICAoWydDJywgJ0YnXSwgWydCJ10pLCAKICAgICAoWydEJ10sIFsnRSddKQogICAgIF0KZmQgPSAoWydBJywgJ0InXSwgWydDJywgJ0QnLCAnRiddKQoKcHJpbnQoIj09IEV4YW1wbGUgZm9yIGlzX2Rlcml2ZWRfZnJvbSB0ZXN0IDI9PT09PT09PT09PT09PT09PT09PT0iKQpwcmludCgiUyA9IHsiLCBlbmQ9JycpCmZvciBGRCBpbiBTOgogICAgcHJpbnQoJyAnLmpvaW4oRkRbMF0pLCAiLT4iLCAgJyAnLmpvaW4oRkRbMV0pLCBlbmQ9IiwgIiBpZiBTLmluZGV4KEZEKSAhPSBTLmluZGV4KFNbLTFdKSBlbHNlICIiKQpwcmludCgifSIpCmRlcml2ZWQgPSBpc19kZXJpdmVkX2Zyb20oZmQsIFMpCnByaW50KCJ7IiArICcgJy5qb2luKGZkWzBdKSwgIi0+IiwgJyAnLmpvaW4oZmRbMV0pKyAifSIsICIgaXMgZGVyaXZlZCBmcm9tIFMgOiIsIGRlcml2ZWQpCnByaW50KCIiKQoKCiMgKDMpLTEuIGlzX2Jhc2lzX29mIO2VqOyImCDsmIjsoJwgMSAoZXhhbXBsZSAzLjExKQpTID0gWwogICAgKFsnQSddLCBbJ0InXSksIAogICAgKFsnQiddLCBbJ0MnXSksIAogICAgKFsnQyddLCBbJ0EnXSkKICAgIF0KCkIgPSBbCiAgICAoWydBJ10sIFsnQiddKSwgCiAgICAoWydCJ10sIFsnQSddKSwgCiAgICAoWydCJ10sIFsnQyddKSwgCiAgICAoWydDJ10sIFsnQiddKQogICAgXQoKcHJpbnQoIj09IEV4YW1wbGUgZm9yIGlzX2Jhc2lzX29mIHRlc3QgMT09PT09PT09PT09PT09PT09PT09PSIpCnByaW50KCJTID0geyIsIGVuZD0nJykKZm9yIEZEIGluIFM6CiAgICBwcmludCgnICcuam9pbihGRFswXSksICItPiIsICAnICcuam9pbihGRFsxXSksIGVuZD0iLCAiIGlmIFMuaW5kZXgoRkQpICE9IFMuaW5kZXgoU1stMV0pIGVsc2UgIiIpCnByaW50KCJ9IikKCnByaW50KCJCID0geyIsIGVuZD0nJykKZm9yIEZEIGluIEI6CiAgICBwcmludCgnICcuam9pbihGRFswXSksICItPiIsICAnICcuam9pbihGRFsxXSksIGVuZD0iLCAiIGlmIEIuaW5kZXgoRkQpICE9IEIuaW5kZXgoQlstMV0pIGVsc2UgIiIpCnByaW50KCJ9IikKYmFzaXMgPSBpc19iYXNpc19vZihCLCBTKQpwcmludCgiQiBpcyBiYXNpcyBvZiBTIDoiLCBiYXNpcykKcHJpbnQoIiIpCgojICgzKS0yLiBpc19iYXNpc19vZiDtlajsiJgg7JiI7KCcIDIoTXlFeGFtcGxlKQpTID0gWwogICAgKFsnQSddLCBbJ0InXSksIAogICAgKFsnQiddLCBbJ0MnXSksIAogICAgKFsnQyddLCBbJ0EnXSkKICAgIF0KCkIgPSBbCiAgICAoWydBJ10sIFsnQiddKSwgCiAgICAoWydCJ10sIFsnQSddKSwgCiAgICAoWydEJ10sIFsnRiddKSwgCiAgICAoWydDJ10sIFsnQiddKQogICAgXQoKcHJpbnQoIj09IEV4YW1wbGUgZm9yIGlzX2Jhc2lzX29mIHRlc3QgMj09PT09PT09PT09PT09PT09PT09PSIpCnByaW50KCJTID0geyIsIGVuZD0nJykKZm9yIEZEIGluIFM6CiAgICBwcmludCgnICcuam9pbihGRFswXSksICItPiIsICAnICcuam9pbihGRFsxXSksIGVuZD0iLCAiIGlmIFMuaW5kZXgoRkQpICE9IFMuaW5kZXgoU1stMV0pIGVsc2UgIiIpCnByaW50KCJ9IikKCnByaW50KCJCID0geyIsIGVuZD0nJykKZm9yIEZEIGluIEI6CiAgICBwcmludCgnICcuam9pbihGRFswXSksICItPiIsICAnICcuam9pbihGRFsxXSksIGVuZD0iLCAiIGlmIEIuaW5kZXgoRkQpICE9IEIuaW5kZXgoQlstMV0pIGVsc2UgIiIpCnByaW50KCJ9IikKYmFzaXMgPSBpc19iYXNpc19vZihCLCBTKQpwcmludCgiQiBpcyBiYXNpcyBvZiBTIDoiLCBiYXNpcykKcHJpbnQoIiIpCgoKIyAoNCktMSBpc19taW5pbWFsIO2VqOyImCDsmIjsoJwxIChleGFtcGxlIDMuMTEpCnByaW50KCI9PSBFeGFtcGxlIGZvciBpc19taW5pbWFsIHRlc3QgMT09PT09PT09PT09PT09PT09PT09PT09PT09PSIpCkIgPSBbCiAgICAoWydBJ10sIFsnQiddKSwgCiAgICAoWydCJ10sIFsnQSddKSwgCiAgICAoWydCJ10sIFsnQyddKSwgCiAgICAoWydDJ10sIFsnQiddKQogICAgXQpwcmludCgiQiA9IHsiLCBlbmQ9JycpCmZvciBGRCBpbiBCOgogICAgcHJpbnQoJyAnLmpvaW4oRkRbMF0pLCAiLT4iLCAnICcuam9pbihGRFsxXSksIGVuZD0iLCAiIGlmIEIuaW5kZXgoRkQpICE9IEIuaW5kZXgoQlstMV0pIGVsc2UgIiIpCnByaW50KCJ9IikKCm1pbmltYWwgPSBpc19taW5pbWFsKEIpCnByaW50KCJCIGlzIG1pbmltYWwgOiIsIG1pbmltYWwpCnByaW50KCIiKQoKIyAoNCktMiBpc19taW5pbWFsIO2VqOyImCDsmIjsoJwyIChteSBleGFtcGxlKQpwcmludCgiPT0gRXhhbXBsZSBmb3IgaXNfbWluaW1hbCB0ZXN0IDI9PT09PT09PT09PT09PT09PT09PT09PT09PT0iKQpCID0gWwogICAgKFsnQSddLCBbJ0InXSksCiAgICAoWydCJ10sIFsnQyddKSwKICAgIChbJ0EnXSwgWydDJ10pCl0KcHJpbnQoIkIgPSB7IiwgZW5kPScnKQpmb3IgRkQgaW4gQjoKICAgIHByaW50KCcgJy5qb2luKEZEWzBdKSwgIi0+IiwgJyAnLmpvaW4oRkRbMV0pLCBlbmQ9IiwgIiBpZiBCLmluZGV4KEZEKSAhPSBCLmluZGV4KEJbLTFdKSBlbHNlICIiKQpwcmludCgifSIpCgptaW5pbWFsID0gaXNfbWluaW1hbChCKQpwcmludCgiQiBpcyBtaW5pbWFsIDoiLCBtaW5pbWFsKQpwcmludCgiIikKCiMgKDUpLTEuIHByb2plY3RfRkRzIO2VqOyImCDsmIjsoJwgKGV4YW1wbGUgMy4xMykKTCAgPSBbJ0EnLCAnQicsICdDJywgJ0QnXSAgICAKUyAgPSBbICAgICAgICAgICAgICAgICAgICAgICAKICAgIChbJ0EnXSwgWydCJ10pLAogICAgKFsnQiddLCBbJ0MnXSksCiAgICAoWydDJ10sIFsnRCddKQpdCkwxID0gWydBJywgJ0MnLCAnRCddCgpwcmludCgiPT0gRXhhbXBsZSBmb3IgcHJvamVjdF9GRHMgdGVzdCAxPT09PT09PT09PT09PT09PT09PT09PT09PT09IikKcHJpbnQoIkwgPSB7IiwgZW5kPScnKQpmb3IgRkQgaW4gTDoKICAgIHByaW50KEZELCBlbmQ9IiwiIGlmIEwuaW5kZXgoRkQpICE9IEwuaW5kZXgoTFstMV0pIGVsc2UgIiIpCnByaW50KCJ9IikKcHJpbnQoIlMgPSB7IiwgZW5kPScnKQpmb3IgRkQgaW4gUzoKICAgIHByaW50KCcgJy5qb2luKEZEWzBdKSwgIi0+IiwgJyAnLmpvaW4oRkRbMV0pLCBlbmQ9IiwgIiBpZiBTLmluZGV4KEZEKSAhPSBTLmluZGV4KFNbLTFdKSBlbHNlICIiKQpwcmludCgifSIpCnByaW50KCJMMSA9IHsiLCBlbmQ9JycpCmZvciBGRCBpbiBMMToKICAgIHByaW50KEZELCBlbmQ9IiwiIGlmIEwxLmluZGV4KEZEKSAhPSBMMS5pbmRleChMMVstMV0pIGVsc2UgIiIpCnByaW50KCJ9IikKClMxID0gcHJvamVjdF9GRHMoTCwgUywgTDEpCnByaW50KCJTMSA9IHsiLCBlbmQ9JycpCmZvciBGRCBpbiBTMToKICAgIHByaW50KCcgJy5qb2luKEZEWzBdKSwgIi0+IiwgJyAnLmpvaW4oRkRbMV0pLCBlbmQ9IiwgIiBpZiBTMS5pbmRleChGRCkgIT0gUzEuaW5kZXgoUzFbLTFdKSBlbHNlICIiKQpwcmludCgifSIpCnByaW50KCIiKQoKCiMgKDUpLTIuIHByb2plY3RfRkRzIO2VqOyImCDsmIjsoJwgKG15IGV4YW1wbGUpCkwgID0gWydBJywgJ0InLCAnQycsICdEJywgJ0UnXQpTICA9IFsKICAgIChbJ0EnXSwgWydCJ10pLAogICAgKFsnQSddLCBbJ0MnXSksCiAgICAoWydCJ10sIFsnQyddKSwKICAgIChbJ0MnXSwgWydEJ10pLAogICAgKFsnRCddLCBbJ0UnXSkKXQpMMSA9IFsnQScsICdCJywgJ0QnXQoKcHJpbnQoIj09IEV4YW1wbGUgZm9yIHByb2plY3RfRkRzIHRlc3QgMj09PT09PT09PT09PT09PT09PT09PT09PT09PSIpCnByaW50KCJMID0geyIsIGVuZD0nJykKZm9yIEZEIGluIEw6CiAgICBwcmludChGRCwgZW5kPSIsIiBpZiBMLmluZGV4KEZEKSAhPSBMLmluZGV4KExbLTFdKSBlbHNlICIiKQpwcmludCgifSIpCnByaW50KCJTID0geyIsIGVuZD0nJykKZm9yIEZEIGluIFM6CiAgICBwcmludCgnICcuam9pbihGRFswXSksICItPiIsICcgJy5qb2luKEZEWzFdKSwgZW5kPSIsICIgaWYgUy5pbmRleChGRCkgIT0gUy5pbmRleChTWy0xXSkgZWxzZSAiIikKcHJpbnQoIn0iKQpwcmludCgiTDEgPSB7IiwgZW5kPScnKQpmb3IgRkQgaW4gTDE6CiAgICBwcmludChGRCwgZW5kPSIsIiBpZiBMMS5pbmRleChGRCkgIT0gTDEuaW5kZXgoTDFbLTFdKSBlbHNlICIiKQpwcmludCgifSIpCgpTMSA9IHByb2plY3RfRkRzKEwsIFMsIEwxKQpwcmludCgiUzEgPSB7IiwgZW5kPScnKQpmb3IgRkQgaW4gUzE6CiAgICBwcmludCgnICcuam9pbihGRFswXSksICItPiIsICcgJy5qb2luKEZEWzFdKSwgZW5kPSIsICIgaWYgUzEuaW5kZXgoRkQpICE9IFMxLmluZGV4KFMxWy0xXSkgZWxzZSAiIikKcHJpbnQoIn0iKQpwcmludCgiIikK