문제

SW Expert Academy에서 디코더 문제를 풀던 중 입력받은 알파벳과 숫자들을 정해진 표에 따라 6비트로 변환한 뒤 이어붙여서 8비트씩 잘라 원본 문자열을 찾아내는 문제를 풀어야 했다. 인코딩된 알파벳의 10진수값은 기존의 utf-8값과 전혀 관계가 없었다. (예를 들면 A는 65, 0은 48이지만 주어진 표에서는 숫자의 ord값이 알파벳보다 컸다. 한마디로 아스키코드표 또는 utf-8변환을 통한 도움을 받을 수 없는 상태)

:: 파이썬 공식문서를 꼼꼼히 참고하여 작성한 글입니다.

::: 이 글은 2020년 2월에 작성한 글을 재업로드 하였습니다.

내가 생각한 문제해결방안

  1. 암호화된 스트링을 정수로변환
  2. 정수를 6자리 2진수로 변환
  3. 취합
  4. 2진수를 8자리씩 끊어서 읽음
  5. 8자리 2진수를 10진수로 변환
  6. utf-8코드표에서 해당 10진수의 값을 읽어오기(문자열 복원)

문제 해결 과정 - 난관 1

제일 시간이 많이 걸렸던 부분은 2번과 5번 이었다. 2번의 경우에는10진수를 2진수로 바꾸는 방법은 어렵지 않게 찾을 수 있었다.

decimal_value = 48

#10진수를 2진수로 변환
format(decimal_value, 'b') => 110000

#10진수를 0b를 포함한 2진수로 변환
format(decimal_value, '#b') => 0b110000
bin(decimal_value) => 0b110000

그냥 10진수를 2진수로 변환하면 되니까 아무 문제가 없다고 생각했었다. 하지만 생각보다 큰 난관이 있었다. 나는 10진수값을 6자리의 2진수로 바꾸어서 취합한 후 8비트로 다시 변환해야 했었다. 다시 말해 7을 2진수로 바꾸었을 때 결과는 111이 아닌 000111이 되어야 한다는 말이었다. 32이상 63이하의 정수는 6자리로 나오게 되어있다. 그렇다면 바꾸고자 하는 숫자가 32보다 작지만 6자리로 출력되어야 할 때에는 어떻게 써야 할까?

답은 '{:06b}'.foramt(decimal_value) 였다! format함수에서 {}안에 추가적으로 인자를 여러개 넣어서 해결했다. 물론 binary_value = '{:06b}'.foramt(decimal_value) 처럼 다른 변수에 결과를 저장하는 것도 가능하다. 간단하게 설명을 하자면 우선 콜론(:)뒤에 있는 b는 결과를 2진수로 출력하라는 뜻이고, 6은 출력하는 결과값이 항상 6자리가 되게 하겠다는 뜻이며, 맨 앞의 0은 빈자리를 전부 0으로 채우겠다는 뜻이다. 이렇게 10진수를 6자리의 2진수로 바꾼 결과를 얻었다.

문제 해결 과정 - 난관 2

다음 난관은 8자리의 2진수(예: 01101010, 00101101 등)를 어떻게 10진수로 바꾸는가 였다. 우선 2진수들의 집합으로 주욱 연결된 원본스트링 011011010010101011… 을 8자리씩 자르는 것은 슬라이싱을 사용하면 다소 어렵지 않게 해결할 수 있을 것이다. 하지만 표기자체는 이미 10진수인 01010101을 다시 10진수로 바꾸려면 어떻게 해야 할까? 비트연산을 이용하여 증가하는 비트( 1 « len(string)와 배열을 통해 해당숫자들을 곱해야 할까? 사실 나는 이 방법을 쓰려고 하나의 추가적인 튜플 bin_tuple = (1, 2, 4, 8, 16, 32, 64, 128)을 준비하고 있었다. (해당 8자리 숫자를 자릿수에 맞춰 저 튜플과 곱해주어 원래의 10진수가 나오게 하려고 했었다) 하지만 이렇게 해결했다면 오늘의 주제인 class int를 얘기하지 않았을 것이다. 우선 10진수로 표기되어 있는 10101001 을 다시 한번 10진수로 바꾸는 방법을 알아보자. 정답을 우선 공개하자면 **저 숫자를 스트링으로 변환한 후 `int('10101001', 2)` 라고 쓰면 된다. 당연히 변수에 대입도 가능하다.**

int(‘10101001’, 2)

강사님께서도 누누히 파이썬 공식 문서를 통한 해결책 접근을 강조하셨었지만 이번 기회를 통해 중요성을 다시한번 통감하게 되었다. 파이썬의 함수는 매개변수로 기본인자값을 설정해 줄 수 있다. 기본인자값이 설정된 매개변수는 전달인자가 들어오지 않는다면 미리 설정된 값을 사용한다. 우리가 보기에는 없는 인자로 보일 수 있지만 함수 내부의 동작에 영향을 분명히 주고 있으며 우리가 새로이 값을 설정해 줄 수도 있다.

파이썬 공식문서 3.7버전의 int 함수에 대한 설명은 다음과 같다.

class int(x, base=10)

Return an integer object constructed from a number or string x, or return 0 if no arguments are given. If x defines __int__(), int(x) returns x.__int__(). If x defines __trunc__(), it returns x.__trunc__(). For floating point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in radix base. Optionally, the literal can be preceded by + or - (with no space in between) and surrounded by whitespace. A base-n literal consists of the digits 0 to n-1, with a to z (or A to Z) having values 10 to 35. The default base is 10. The allowed values are 0 and 2–36. Base-2, -8, and -16 literals can be optionally prefixed with 0b/0B, 0o/0O, or 0x/0X, as with integer literals in code. Base 0 means to interpret exactly as a code literal, so that the actual base is 2, 8, 10, or 16, and so that int('010', 0) is not legal, while int('010') is, as well as int('010', 8)

해석을 하자면,

int(x, base=10) int 함수는 기본인자로 10을 가지고 있다. 기존에 알고있던 스트링이나 실수를 int()함수로 감싸 정수로 바꿔주는 용도로 주로 사용했는데 알고보면 뒤에 우리가 인자를 하나 넣을 수 있다는 말이다.

두번째 문단 첫번째 문장부터 이 기본인자에 대해 설명을 해주고 있다. ‘x가 숫자가 아니거나 base가 주어졌다면, 첫번째 전달인자 x는 정수형 리터럴을 표현하는 스트링,바이트, 또는 바이트어레이 객체 여야 한다’ 라고 써있다. base의 기본값은 10 이고 0 또는 2부터 26까지의 정수가 올 수 있다. base값으로 n이라는 정수를 선택할 경우 해당 리터럴(x의 정수형 리터럴)은 0~n-1까지의 숫자로 구성될 수 있으며 2, 8, 16을 base값으로 넣는 경우 선택적으로 숫자 대신 0b,0B, 0o,0O, 0x,0X를 사용할 수 있다. 그리고 base값으로 된 이 숫자들은 정수형리터럴을 해당 진수로 계산한 결과를 10진수 형태로 알려준다. 간단한 예시를 보자.

print(int('1010', 2))
>>> 8 * 1 + 4 * 0 + 2 * 1 + 1 * 0 = 10

print(int('1010', 8)) 
>>> 8^3 * 1 + 8^2 * 0 + 8^1 * 1 + 1 * 0 = 520

print(int('1010', 16)) 
>>> 16^3 * 1 + 16^2 * 0 + 16^1 * 1 + 1 * 0 = 4112

다음과 같다. 동일한 정수형 리터럴인 스트링 ‘1010’은 주어진 기본값의 진수로 인식된다. 따라서 같은 리터럴이지만 기본값이 달라짐에 따라 최종적으로 반환되는 10진수의 값이 달라지는 것을 확인 할 수 있다. 그렇다면 기본인자가 n인 경우 0 ~ n-1까지의 정수를 사용할 수 있다고 했는데 그렇지 않다면 어떻게 될까?

print(int('1012', 2)) 
>>> ValueError: invalid literal for int() with base 2: '1012' 

ValueError가 발생하였다! int()를 사용할 때 내가 입력한 리터럴’1012’는 base=2와는 같이 사용할 수 없다고 한다. 우리는 이미 답을 알고 있다. 맨뒤에 2라는 숫자가 들어있기 때문이다.

공부한 내용은 여기까지이며 그렇게 한 문제를 오늘도 풀어냈다.

번외 - class int()의 다양한 사용법

추가적으로 int()의 주 사용 용도중 하나인 실수를 정수로 변환하는 기능에 대해서도 조금 더 알아보았다. 실수 3.14가 들어가면 어떻게 될까? 아마 3이겠지. 3에 가까우니까. 그렇다면 3.48은? 3.9는? 3.5부터 반올림이 되던가? 된다면 음수는? 반올림을 음수에 적용해 봤던가? 그럼 -3.14는 -3이 될까 -4가 될까?

실수를 정수로 바꾸는 함수는 math 라이브러리에 3개, 기본 라이브러리에 1개가 있다. math라이브러리에 내장되어 있는 ceil(), floor(), trunc(), 그리고 아무것도 import하지 않아도 사용할 수 있는 round(). 우리가 흔히 알고 있는 반올림은 round() 함수이다. ceil()은 무조건 x보다 위(천장)에 있는 수, 즉 값을 증가시키다가 만나는 첫번째 정수를 반환한다. floor는 반대로 아래(바닥)에 있는 가장 가까운 정수를 반환한다. 그럼 trunc는 0에 가까운 쪽으로 가다가 만나는 정수를 반환한다. 우리가 사용하는 int()는 trunc()와 정확하게 같은 동작을 수행한다. 그럼 round()와 trunc()의 차이가 없다고? 아니아니, 차이가 있다. 우선 정답부터 보자.

Screen Shot 2020-03-26 at 22.25.57

  1. round(): 우리가 상식적으로 알고있는 반올림 연산을 수행한다. 음수의 경우에는 부호를 제외하고 반올림 연산을 진행한뒤 부호를 붙여서 리턴한다.

  2. int() 와 trunc(): 이 두개의 연산은 파이썬 내부에서 일어나는 연산이 정확하게 일치하여 결과가 같다. 두개를 같이 쓴 이유이기도 하다. 이 연산은 무조건 0에 가까운 방향으로(양수이면 floor(), 음수이면 ceil() 연산이 된다) 결과값을 만든다.

  3. floor(): floor, 말 그대로 ‘바닥’이라는 뜻이다. 숫자가 양수일 경우 해당 숫자보다 크지 않은 정수 중 제일 큰 값을 반환한다. 음수의 경우, 해당 실수보다 작은 정수 중 가장 큰 값을 반환한다.

  4. ceil(): ceil, 말 그대로 ‘천장’이라는 뜻이다. 숫자가 양수일 경우 해당 숫자보다 큰 정수 중 제일 작은 수를 반환한다. 음수의 경우 해당 실수보다 큰 정수 중 가장 작은 값을 반환한다.

이렇게 첫 글을 써 보았다. 사실 이 문제를 푼지는 어언 한달(…) 이 다되어 간다. 지금 진도를 간단하게 설명해 보자면, 비록 재택학습때문에 집중도 안되고 공부도 안되지만 파이썬의 경우 클래스까지 진도를 나갔고, 알고리즘 수업은 List, Sort, String, Stack1, Stack2까지 진도를 끝냈다. 한달동안 참으로 다양한 알고리즘들을 배웠다. Memoization, DP(동적 프로그래밍), DFS(깊이우선탐색, Depth First Search), Backtracking, Divide&Conquer(분할정복)을 수업에서 배웠다. 코로나 바이러스로 인해 그 이후과정은 재택학습으로 진행하게 되어서 Queue와 그 활용개념인 BFS , Linked list(연결 리스트), Tree 구조들은 혼자 공부했다. 여담이지만 처음 DFS를 배우고 구현할 때 거의 울면서 했다. 수학과 컴퓨터에 자신이 없진 않았는데 이해도, 구현도 되지 않아 이 악물고 했다… 거기다가 HTML과 CSS, Bootstrap을 이용한 flex, grid system을 이용한 정적 웹사이트 구현, JavaScript기초 등 프로그래밍 언어와, 알고리즘들과, 웹의 기초를 익혔다.

사실 공부를 시작하기 전에는 ‘내가 과연 진도를 따라는 갈 수 있을까’, ‘너무 뒤쳐지면 그냥 조용히 나오는게 맞으려나’ 하는 생각도 있었지만 너무나도 좋은 강사님과, 너무나도 알찬 교육과정과, 무엇보다도 너무나도 좋은 사람들을 많이 만났다. 우리반 한명 한명 모두 저마다의 사연이 있고, 하고자 하는 의지가 있고, 같이 해내자는 마음으로 혼자라면 엄두도 못 냈을 양들을 소화하면서 버텼다. 비록 지금은 코로나 바이러스로 인해 재택학습이 진행중이지만… 언젠가는 다시 볼 날이 있겠지!

넘나좋은 \(ㅇㅅㅇ)/ 첫 포스팅! 여기서 마치겠습니다!!