코딩 테스트/Level 2

46. 후보키

컴닥 2020. 8. 29. 12:52
반응형

후보키
2019 KAKAO BLIND RECRUITMENT
1798명 완료

풀이 자체는 어렵지 않으나, 
파이썬에서 zip의 사용법을 모르면
코딩이 조금 까다롭다. 

https://programmers.co.kr/learn/courses/30/lessons/42890

 

코딩테스트 연습 - 후보키

[["100","ryan","music","2"],["200","apeach","math","2"],["300","tube","computer","3"],["400","con","computer","4"],["500","muzi","music","3"],["600","apeach","music","2"]] 2

programmers.co.kr

머리는 적게 쓰고 컴퓨터를 많이 쓰는
훌륭한(?) 전수조사(brute force) 알고리듬을 만들자. 

나의 머리는 소중하니까..
많이 부려먹으려고 비싼 컴퓨터 산 거 아님?

각 칼럼의 조합 즉 nCr을 r = 1 ~ len(relation)까지 만들고.. 

각 조합의 유일성을 확인하고.. 
(파이썬에는 딕셔너리나 집합을 사용하면 편하다..)

유일성을 통과한 조합을 A,
유일성과 최소성 확인을 이미 통과한 조합들을 B 라할 때 
B는 A의 부분 집합이 아니어야 한다. 

그런데 zip의 사용법을 잊어버려서 한참을 헤맸다...
8개월 만에 코딩이라..
zip함수를 만들까까지 고민했다.

list 2개를 인자로 넣으면 잘 작동된다. 

list(zip([1, 2, 3], [4, 5, 6]))
[(1, 4), (2, 5), (3, 6)]

list 2개를 1개의 list로 묶어서 zip에 보내면 작동하지 않는다 ㅠ,.ㅠ 

list(zip([[1, 2, 3], [4, 5, 6]]))
[([1, 2, 3],), ([4, 5, 6],)]

이럴 때 *를 이용하면 포장을 하나 벗겨준다.. 

list(zip(*[[1, 2, 3], [4, 5, 6]]))
[(1, 4), (2, 5), (3, 6)]

이제 떠올랐으니 일사천리 코딩....

def solution(relation):
    from itertools import combinations
    r = [tuple(each) for each in zip(*relation)]
    combies = [combi for i in range(len(r)) for combi in combinations(r, i + 1)]
    candidate_keys = []
    for combi in combies:
        zip_elements = list(zip(*combi))
        if len(zip_elements) == len(set(zip_elements)):  # 유일성 확인
            for candidate_key in candidate_keys:  # 최소성 확인. 지저분...
                for element in candidate_key:
                    if element not in combi:
                        break
                else:  # 브레이크가 없었다 = 모든 원소가 다 들어 있었다 = 최소성에서 탈락
                    break  # 탈락한 분들은 브레이크
            else:
                candidate_keys.append(combi)
    return len(candidate_keys)

일단 이렇게 풀어봤는데... 최소성 확인하는 부분이 좀 지저분...

11번 테스트 케이스에서 걸려 버렸다.. 
11번이 이런 케이스가 아닐까?

[["a", "a"], ["b", "b"]]  # 2 가 나와야 하는데 1 이... 

1번 2번 컬럼 모두 고유 키로 사용해도 되지만 위 코드에서는 이를 중복으로 본다.. 

예를 들자면 이름과 전공이 art 인 사람.
이름과 전공이 music 인 사람이 있을 때,
이름을 고유 키로, 전공을 고유 키로 해도 된다.. 

그래서 직접 비교하지 않고 인덱스를 이용하도록 코드를 고쳤다.. 
지저분한 부분도 같이 수정.

def solution(relation):
    from itertools import combinations
    r = [tuple(each) for each in zip(*relation)]
    combies = [set(combi) for i in range(len(r)) for combi in combinations(range(len(r)), i + 1)]
    candidate_keys = []
    for combi in combies:  # combi에는 (실제 원소?가 아닌) 숫자 인덱스의 조합만 들어있다..
        elements = [r[index] for index in combi]  # 실제 (원소?들의) 조합을 만들기 위해서 리스트를 하나..
        zip_elements = list(zip(*elements))
        if len(zip_elements) != len(set(zip_elements)):  # 유일성 테스트
            continue
        for candidate_key in candidate_keys:  # 최소성 테스트
            if candidate_key.intersection(combi) == candidate_key:
                break
        else:
            candidate_keys.append(combi)
    return len(candidate_keys)
테스트 1 〉	통과 (0.07ms, 10.8MB)
테스트 2 〉	통과 (0.08ms, 10.7MB)
테스트 3 〉	통과 (0.08ms, 10.6MB)
테스트 4 〉	통과 (0.08ms, 10.8MB)
테스트 5 〉	통과 (0.10ms, 10.7MB)
테스트 6 〉	통과 (0.05ms, 10.9MB)
테스트 7 〉	통과 (0.05ms, 10.8MB)
테스트 8 〉	통과 (0.05ms, 10.6MB)
테스트 9 〉	통과 (0.09ms, 10.8MB)
테스트 10 〉	통과 (0.11ms, 10.8MB)
테스트 11 〉	통과 (0.13ms, 10.8MB)
테스트 12 〉	통과 (0.62ms, 10.6MB)
테스트 13 〉	통과 (0.20ms, 10.8MB)
테스트 14 〉	통과 (0.08ms, 10.7MB)
테스트 15 〉	통과 (0.07ms, 10.7MB)
테스트 16 〉	통과 (0.08ms, 10.7MB)
테스트 17 〉	통과 (0.08ms, 10.7MB)
테스트 18 〉	통과 (1.06ms, 10.7MB)
테스트 19 〉	통과 (0.70ms, 10.7MB)
테스트 20 〉	통과 (1.15ms, 10.6MB)
테스트 21 〉	통과 (0.82ms, 10.9MB)
테스트 22 〉	통과 (0.62ms, 10.8MB)
테스트 23 〉	통과 (0.09ms, 10.7MB)
테스트 24 〉	통과 (0.74ms, 11MB)
테스트 25 〉	통과 (1.00ms, 10.8MB)
테스트 26 〉	통과 (0.84ms, 10.9MB)
테스트 27 〉	통과 (0.19ms, 10.5MB)
테스트 28 〉	통과 (0.20ms, 10.7MB)

다행히 통과...

반응형