[알고리즘] 비밀지도(비트 연산자 )

^_^·2022년 11월 9일
0

비밀지도

프로그래머스 레벨 1문제 중 카카오 코딩테스트1차 비밀지도 문제를 풀다 알게된 비트 연산에 대해 정리한다.

문제는 아래와 같았다.

비밀지도
네오는 평소 프로도가 비상금을 숨겨놓는 장소를 알려줄 비밀지도를 손에 넣었다. 그런데 이 비밀지도는 숫자로 암호화되어 있어 위치를 확인하기 위해서는 암호를 해독해야 한다. 다행히 지도 암호를 해독할 방법을 적어놓은 메모도 함께 발견했다.

  • 지도는 한 변의 길이가 n인 정사각형 배열 형태로, 각 칸은 "공백"(" ") 또는 "벽"("#") 두 종류로 이루어져 있다.
  • 전체 지도는 두 장의 지도를 겹쳐서 얻을 수 있다. 각각 "지도 1"과 "지도 2"라고 하자. 지도 1 또는 지도 2 중 어느 하나라도 벽인 부분은 전체 지도에서도 벽이다. 지도 1과 지도 2에서 모두 공백인 부분은 전체 지도에서도 공백이다.
  • "지도 1"과 "지도 2"는 각각 정수 배열로 암호화되어 있다.
  • 암호화된 배열은 지도의 각 가로줄에서 벽 부분을 1, 공백 부분을 0으로 부호화했을 때 얻어지는 이진수에 해당하는 값의 배열이다.

입력 형식
입력으로 지도의 한 변 크기 n 과 2개의 정수 배열 arr1, arr2가 들어온다.

1 ≦ n ≦ 16
arr1, arr2는 길이 n인 정수 배열로 주어진다.
정수 배열의 각 원소 x를 이진수로 변환했을 때의 길이는 n 이하이다. 즉, 0 ≦ x ≦ 2n - 1을 만족한다.

출력 형식
원래의 비밀지도를 해독하여 '#', 공백으로 구성된 문자열 배열로 출력하라.

내가 처음 생각한 방법은 bin()함수로 arr1과 arr2를 더해 각 자릿수가 0보다 크면 #로 치환하고 0이면 공백으로 치환하는 방법을 사용했다.

def solution(n, arr1, arr2):
    list_1 = [int(bin(i)[2::]) for i in arr1]
    list_2 = [int(bin(i)[2::]) for i in arr2]
    answer = [str(x+y).zfill(n) for x,y in zip(list_1, list_2)]
    res = []
    for i in answer:
        i = str(i).replace( '1' , "#")
        i = str(i).replace( '2' , "#")
        res.append(str(i).replace( '0' , " "))
    return res

이따위로 밖에 생각 하지 못하는 내가 싫다는 생각을 하면서 다른사람들의 풀이를 봤다.

그러다 아래 코드를 보게되었는데...
????????????????????

def solution(n, *maps):
    return [line(n, a | b) for a, b in zip(*maps)]


def line(n, x):
    return ''.join(' #'[int(i)] for i in f'{x:016b}'[-n:])

다른 사람들의 풀이는 어느정도 이해를 하고 '아~이런 방법도 있구나~' 정도였지만 위 코드는 뭔지 모르겠다. 그냥 모르겠다.
나는 위 코드가 좋은 코드인지 실행시간면에서 유리한 코드인지 보다 저 코드 자체가 너무 궁금했다. 왜 저게 돌아가는지. 어떻게 이런 생각을 할 수 있는지.
특히 a | b이게 line함수에 인자로 넘어가는데 이게 뭔소린지....

여기까지 내가 비트연산에서 | 가 무슨 역할을 하는지 찾게된 과정이다.

비트연산자

0b110011001
0b100110101
비트 연산이란 위와 같은 2진수의 숫자열을 같은 자리끼리 &, |, ^, ~ 등의 비트 연산자로 연산하는것을 말한다.

& 연산자

AND 연산자로 두 조건 모두 만족할때 참인 연산자인데 비트 연산에서는 각자리수 가 일치하면 1(True)또는 0(False)으로 연산한다.즉 and연산자의 1 & 1 = 1, 1 & 0 = 0, 0 & 1 = 0, 0 & 0 = 0의 규칙에 따라서 계산해주면 된다.

| 연산자

OR 연산자이다. 두조건 중 하나라도 만족하면 참이다. 비트연산에선? 둘중 하나라도 1이면 1이라는 말이다.

^ 연산자

0b1100 ^ 0b1001 = 0b0101
^연산자는 XOR연산자 라고 하는데 두 수가 서로 다른 값을 가질 때, 1을 반환한다.

~ 연산자

~0b1100 = 0b0011 = 0b11
~연산자는 not과 같은 일을 한다.
~연산자는 모든 비트를 반전시킨다. 이 때, 앞 자리의 0은 생략할 수 있다.

다시 코드를 보자.

def solution(n, *maps):
    return [line(n, a | b) for a, b in zip(*maps)]


def line(n, x):
    return ''.join(' #'[int(i)] for i in f'{x:016b}'[-n:])

solution 함수는 n과 *maps 를 인자로 받는데 *maps는 사실 arr1, arr2 이다. arr1, arr2의 요소가 a와 b로 전달된다. 여기서 OR 비트 연산자로 계산된 값이 10진수로 x로 전달 된다.
이제 2진수 포매팅이다. 파이썬 3.6부터 f-string 이라고 불리는 문자열을 좀 더 쉽게 포멧팅하기 위한 새로운 방법이 추가되었다.f'{x:016b}'[-n:1] 으로 전달된 x를 16자리의 2진수로 바꿔준다. f'{x:016b}'가 무슨 말인지도 몰라서 찾아봤다. 파이썬 문자열 포메팅은 표현식을 지원하기 때문에 f'{x:016b}'와같이 사용할 수 있고 016b에서 16b는 16자리 2진수로 표현하겠다는 말이고 0은 표현한 2진수가 16자리가 안될때 0으로 채워주겠다는 말이다.
이제 2진수로 변환한 문자열을 반복하며 int(i)에 넣어준다 0이라면 " #"에 index[0]인 빈 공백이 들어갈 것이고 1이라면 index[1]에 해당하는 #이 들어간다.

코드이해 하는데 너무 오래 걸렸다. 이해는 했지만 아직 저런 생각을 할 수 있을지 자신이 없다. 잘 활용 할 수 있을때 까지 자주 봐야겠다.

0개의 댓글