무효 클릭 IP 추적 중...
파이썬/파이썬 중급

[파이썬 중급] 진법 변환(N진수 -> 10진수 | 10진수 -> N진수)

꼬예 2021. 6. 18.

이번 시간에는 진법 변환에 관해 알아보겠습니다. 

 

 

 

N진수 -> 10진수으로 바꾸는 법

N진법을 10진법으로 바꾸는 방법은 아주 간단합니다. 

바로 int() 함수를 사용하면 됩니다!

int()요..? 네 우리가 아는 그 string을 int로 바꿔주는 그 함수입니다.

 

우리가 진법을 바꾸는 기능이 있는지 몰랐던 이유는 int()함수내 디폴트로 값이 이미 설정되어 있기 때문입니다.

 

코드를 통해 확인을 해볼게요.

 

2진수를 10진수로 바꾸고싶다면..

d

위와 같이 코드를 작성해주면됩니다.

첫번째 인자로 올 값은 string 형태로 이진법 숫자가 와야되구요. 두번째 인자에는 파이썬에게 "이건 '2진법 숫자'를 '10진법'으로 바꾸는거야 "라는 걸 알려주기 위해 이진법을 의미하는 2 라는 숫자를 넣어주었습니다.

 

만약 16진법 숫자를 10진법으로 바꾸고싶다면 어떻게 할까요?

마찬가지로 16진법 숫자를 string 형태로, 첫번째 인자로 넣어줍니다.

(참고로 "A12F"와 같이 알파벳에 대문자로 와도 상관없습니다.)

 

이번에는 base = 16이라는 형태로 keyword-argument형태로 넣어봤습니다.(변수 = 값 형태를 keyword-argument라 부름)

 

위처럼 인자 명칭을 안넣는 형태인 positional-argument로 넣든 인자 명칭을 넣은 keyword-argument형태로 적든 상관없습니다:)

 

조심해야 될점으로는 "ValueError: invalid literal for int() with base" 에러입니다. 

첫번째 인자로 들어간 "B"는 11진법에 포함되지 않는 문자이기 때문에 발생되는 에러입니다.

(11진법 숫자는 0123456789A 안에 포함된 문자만 사용가능합니다.)

 

즉, 첫번째 인자로 진수를 넣을때 이값이 몇진수인지는 알고 집어넣어야 실수할 일이 없겠죠?

 

지금까지는 N진수를 10진수로 바꾸는걸 알아봤으니,

거꾸로 10진수를 N진수로는 어떻게 바꿀까요?

 

 

N진수 -> 10진수

감사하게도 파이썬에서는 N진수에서 10진수로 바꿀수 있는 built-in function들이 제공 되고 있습니다. 

 

함수명칭또한 직관적인데요!

1. bin() --> binary(2진법)

위와 같이 bin함수에 10진수 숫자를 넣어주면 알아서 2진수로 변경이 됩니다. 

결과값을 보니 조금 불편한 문자들이 붙어있는데요..  앞에 prefix형태의 0b가 붙어 있습니다.

이 문자가 이진수는걸 의미하는 문자입니다.

 

위와 같이 우리가 익숙한 가로안에 작은 숫자 형태로 몇진법인지 나타내는 역할을 파이썬에서는 저렇게 적어준다고 생각하시면 됩니다.

 

그럼 진수가 다르면 prefix형태도 다르겠죠?

 

이어서 다른 진수도 알아보겠습니다. 

 

2. oct() --> octal(8진법)

3. hex() --> hex(16진법)

 

각 진법마다 0이 앞에붙고 뒤에서 문자가 조금씩 다른 형태인걸 눈치채셨나요?

사용법은 같으니 자세한 설명은 생략하겠습니다:)

 

 

 

앞서 배운 내용에 조금 첨언을 하자면,

위 코드를 보시는것처럼 prefix를 붙힌 상태의 값을 넘겨도 10진법으로 바꾸는데 문제가 없다는 것도 팁으로 알고 가시면 좋을 것같습니다. 

 

자! 여기서 궁금증이 있으실거 같은데요.

그럼 위에 3가지 진법 외에 다른 진수를 10진수로 바꾸려면 어떻게 해야할까요? 

 

안타깝게도... 직접 제작을 해야합니다..

 

직접 제작을 하기 위해  간단하게 진법에 대해 이해를 하고 가는 시간을 가지도록 하겠습니다..

이미 아시는 분은 스킵하시면 됩니다. 

 

위 예는 10진수를 5진수로 바꾸는 예인데요.

232를 5로 계속 나누어주는 과정을 더이상 나누어지지 않을때 까지 반복합니다.

그후 나머지값들을 역순으로 써주면 자연스럽게 5진수로 변경되는 내용입니다. 

(초록 글씨나눈 몫이구요. 파란색 글씨나머지입니다. )

 

그런데 우리는 코딩을 하는것이기 때문에 좀더 명확한 규칙을 세우면 좋겠죠?

1을 한번더 나눠줘서 5진수의 숫자를 나머지로만 구성 한다면 훨씬 깔끔한 코딩이 가능 할겁니다. 

아래와 같이 말이죠.

자! 이제 수식을 코드로 구현해볼 차례인데요.

 

우선 생각해볼수 있는것은..

첫번째! 몫이 0이 되는 순간, 코드를 멈추게 하고 싶습니다.

두번째! 나머지값들 나열하되, 가장 나중에 연산된 나머지가 문자열에서 가장 앞에 오게 하고싶습니다.

 

간단하죠? 이 두가지만 염두해두시면 됩니다.

 

1. 우선 저는 함수를 만들어볼건데요.

두개의 파라미터(최초 나누어질 값, 나눌값(몇진법인지?))를 구성해보겠습니다.

 

최초로 나누어질 값은 n , 나눌값을 b로 정의 하겠습니다. 

def from_base10(n, b):
	pass

 

2. 다음으로는 몫이 0이 딱! 되는 순간까지 나머지들을 나열하고 싶은데 , 이 숫자들이 역순으로 나열되었음 좋겠습니다. 

def from_base10(n, b):
    digits = [ ]

    while n > 0:
        m = n % b # 나머지
        n = n // b # 몫
        digits.insert(0, m) # 우리가 필요한 숫자는 나머지
    return digits

저는 while 문을 이용해서 n(몫) 이 0이 되는 순간 함수를 끝내는 형태를 만들어 보았구요.( n > 0 클때까지만 while문 실행)

 

insert 를 이용해 새로 추가되는 나머지가 제일 앞으로 가도록 insert(0, m) 메소드를 이용했습니다. 

그러면 늦게 들어온 나머지값이 가장 앞에 위치할 수 있겠죠? 

 

제법 그럴싸해진것 같은데요.

 

하지만 몇가지 조건을 추가해야합니다.

여기서 우리가 다룰 n은 양수이고요, 또 2진법 이상의 숫자들만 다룰겁니다. 

또한 그럴일은 없겠지만.. 최초 숫자가 0일 경우는 while 문 안에 아예 들어가질 않으니 이 경우는 반례로 따로 지정을 해줘야합니다

 

아래와 같이 말이죠..

def from_base10(n, b):
    
    if b < 2:
    	raise ValueError('Base b must be >=2')
    if n < 0:
    	raise ValueErorr("Number n must be >=0")
    if n==0: return [0] # 0을 리스트안에 담은 상태로
    digits = [ ]
    while n > 0:
        m = n % b # 나머지
        n = n // b # 몫
        digits.insert(0, m) # 우리가 필요한 숫자는 나머지
    return digits

여기서 왜 0을 리스트에 담았는지 뒤 코드를 보시면 자연스럽게 이해되실테니 계속 진행하도록 하죠. 

 

+ 보너스

나머지와 몫을 위처럼 계산 해주는 방법 뿐만 아니라 divmod 함수를 이용하면

튜플형태로 한번에 몫과 나머지를 반환한다는 점도 챙겨가시길 바랍니다.

def from_base10(n, b):
    
    if b < 2:
    	raise ValueError('Base b must be >=2')
    if n < 0:
    	raise ValueErorr("Number n must be >=0")
    if n==0: return [0] # 0을 리스트안에 담은 상태로
    digits = [ ]
    while n > 0:
    	n, m = divmod(n, b) # n = 몫, m = 나머지
        digits.insert(0, m) # 우리가 필요한 숫자는 나머지
    return digits

우선 중간 점검으로 코드를 실행해보겠습니다. 

코드를 실행하면 아래와 같은 숫자가 나옵니다. 

그런데! 여기서 12, 13 가 (0~9) 숫자보다 큰 경우가 발생했는데요.

이때는 특정 문자로 바꿔 줘야됩니다.

어떤 문자로 바꿔줘야하는지는 여러분들이 원하는 문자를 넣으시면됩니다. 다만 타인이 알아볼수 있도록 주석을 달아 놓아야 겠지요? :)

 

하지만 파이썬은 기본적으로 9이상부터는 0123456789abcd.....z  식으로 알파벳으로 나열 하기때문에,  파이썬 식으로 진행해보죠.

우선 16진수를 구한다고 할경우 필연적으로 이미 값자체가 0~9 이상의 숫자를 필요로 합니다. 

예를들어 16진수를 구할 경우 최대 나머지가 15인 숫자도 나올수 있기때문에.

적어도 '0123456789abcdef' 와 같이 15개의 문자열을 구성해야겠죠?

 

 

이번에는 해당 진수값이 0~9이상일 경우를 대비한 코드입니다. 

당연히 위에서 언급한 맵핑할 문자열도 구성을 할거구요.

 

코드를 통해 설명해보겠습니다.

우선 두개의 파라미터로 구성된 함수이고요.

첫번째 파라미터 digits은 앞서 from_base10에서 나온 리스트를 넣을값이고, digit_map은 맵핑할 문자열입니다.

def encode(digits, digit_map):
    if max(digits) >= len(digit_map): 
        raise ValueError("digit map is not long enough to encode the digits")
    
    encoding =''
    for d in digits:
        encoding += digit_map[d]
    return encoding   

digits 리스트의 max 함수를 통해 원소값 중 최대값이 우리가 임의로 만들 digit_map의 갯수보다 같거나 작은지를 확인해야합니다. 

 

만약 digit_map의 갯수보다 digtis커버리면 맵핑할 숫자가 없으니까 문제가 되겠죠?

그래서 위와같이 조건절을 통해 digit_map 의 갯수가 더적을 경우는 에러를 발생시킵니다.

 

다음으로는 digits 값을 for문을 이용해 digit_map에 있는 값으로 대체하는 작업입니다. 

d가 10이라면 당연히 인덱스 번호 10에 위치한 a로 자동적으로 값이 변경되겠죠.?

 

그 후 변경된 값들은 encoding 변수에 차곡차곡 쌓이게됩니다:)

 

한번 예시로 코드를 실행시켜 볼게요

 

자! 이제 거의 끝이 보이네요

위에서 완성 한 함수들을 합치는 코드를 만들어 보겠습니다.  

def rebase_from10(number, base):
    digit_map = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    
    if base < 2 or base > 36: # digit_map의 개수가 36개 그이상은 안됨..
        raise ValueError('Invalid base: 2<= base <=36')
        
    digits = from_base10(number, base)
    encoding = encode(digits, digit_map)
    
    return encoding

최종 함수에서는 digit_map을 지정해서 변수에 넣어두었습니다.

그리고 digit_map의 갯수가 36개 이기때문에 그이상의 숫자(base)가 들어올경우는 에러를 발생시키도록 작성해 놓았습니다.

나머지는 위에서 작성한 함수를 그대로 변수에 넣은 형태니 쉽게 이해되실거라 생각합니다. ^^

 

아래는 최종 결과입니다.

연습 문제

지금까지 배운것을 이용하여 알고리즘 문제를 풀어보겠습니다. 

 

첫번째 문제는 

백준 1373번 문제 입니다.

 

해당 문제는 2진수를 8진수로 바꾸는 문제인데요.
지금 까지 우리가 배운건 N진수를 10진수 또는 10진수를 N진수로 바꾸는 식이었죠.
즉 해당 문제도 10진수로 우선 바꾼다음 2진수로 바꾸는 식으로 진행을 해보면 될것 같습니다.

 

1. 우선 2진수를 10진수로 바꾸는 법은 int() 함수를 이용해주면 간단합니다.

 

2. 다음으로 10진수로 바뀐값을 8진수로 바꾸어야 하는데요.. 기억나시나요?
   2, 8, 16 진법은 파이썬의 built-in 함수가 있습니다!

   바로 oct 함수죠? 기억이 안나신다면 위에가서 다시한번 보고 오십시오!

 

3. 마지막으로 output은 prefix 형태의 문자가 뜨기때문에,  해당문자를 제외하기위해, 문자열 슬라이싱을 통해 최종 마무리를 하겠습니다.

n = int(input(),base=2)
print(oct(n)[2:])

 

  • 트위터 공유하기
  • 페이스북 공유하기
  • 카카오톡 공유하기
이 컨텐츠가 마음에 드셨다면 커피 한잔(후원) ☕

댓글