# ---------------------------------------------------------------------------- 
# -- 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