변명을 굳이 하자면….😅

지난 2월에 글을 쓰고 이제 1주일에 적어도 1개씩 글을 쓰려 했었다.

하지만 알다시피 “그 바이러스🧫” 때문에 내가 글을 쓰러 가던 도서관이 폐쇄되고 역시, 나의 귀차니즘으로 인해서 글이 잘 써지지 않았다. 엎친 데 덮친 격으로 3월에 대면 수업이 예정이었던 학교는 계속 대면 수업을 2주씩 연기하다가 결국 사태가 안정될 때까지 무기한 연기에 들어갔다. 그러면서 과제로 출석을 인정하는 온라인 강의가 진행되면서 나의 많은 시간이 과제를 마무리하고 강의를 듣는 데 사용되었다.

그럼에도 불구하고 내가 진행하던 작은 프로젝트가 있었기 때문에 코딩도 멈출 수가 없었다.

그래서 여유롭게 글을 쓸 시간이 부족했고 2월에 쓰기 시작한 글을 4월에 마무리 짓는 점을 너그럽게 이해 부탁한다. 😊

시작🚩

고등학교 3학년 수능 이후, 학생들 거의 모두가 각자의 지원 학교의 합격 소식을 기다리고 있었다.

서로 수다를 떨기도 하고 게임도 하고 음악을 듣는 등 각자마다 남은 고등학교 생활을 알차게 보내고 있는 중이었다.

나는 음악을 듣고, 잠을 자기도 하고, 페이스북 피드를 계속 내리면서 고등학교 생활의 마지막을 최대한 게으르게(?) 보내고 있었고 이럴 바에는 코딩이라도 하자 해서 학교에 노트북을 가져와서 혼자 코딩을 시작했다. (물론 그리 오래가지는 못했다)

주로 했던 것은 입시 때문에 그동안 미뤄 놓았었던 알고리즘 공부를 했었고 주로 이용한 웹사이트는 그 유명한 acmicpc 즉 Baekjoon Online Judge이었다.

그날도 어김없이 교실의 먼지 쌓인 창문으로 들어오는 햇살🌞과 함께 잠에 덜 깬 눈😪을 비비면서 문제를 풀기 시작했다.

문제를 풀면 쉽게 해결한 적도 많았지만 어려운 문제는 거의 3시간 가까이 생각해서 푼 문제도 많이 있었다. (물론 더 걸린 문제도 많았다)

이번 문제도 3시간 이내에 끝날 줄 알았지만 결국 집에 가서까지 문제를 풀지 못하고 귀찮았던 나는 BOJ에서 문제 푸는 것을 멈추고 리액트에 관심을 가지고 벨로퍼트님의 블로그를 기웃거리면서 개인 프로젝트를 시작했었다.

그러던 도중 내가 풀지 못한 그 문제가 생각이 나서 다시 도전하기로 했다.

야,2941너지?🤨

그 문제는 바로 2941번 이름마저 특이한 “크로아티아 알파벳”

사실 쉬운 문제다.

아마 내가 이 문제를 풀지 못했던 이유는 파이썬 문자열 메소드인 replace()에 대해 몰랐고 직접 구현하려고 하다가 진이 빠져서 포기한 게 아닌가 싶다. (솔직히 좀 부끄럽다😓)

아무튼 문제를 대충 설명하면

옛날 옛적에는 컴퓨터 운영체제에서 크로아티아 알파벳(Wikipedia에서는 Gaj’s Latin alphabet이라고 하더라)을 완전하게 표현할 수 없었기에 일반 알파벳 + 특수 문자 or 다른 알파벳의 조합으로 표현했었다는데(문제에 어떻게 표현 하는지는 적혀있으니 위에 링크를 확인하도록 하자) 문제는 특정한 문자열이 주어졌을 때 이 문자열에 포함되어 있는 크로아티아 알파벳의 개수를 찾는 문제였다.

ex_screenshot <Gaj’s Latin alphabet>

사진출처: 영문 위키피디아 <Gaj’s Latin alphabet> 문서

사실 엄밀히 말하자면 위에 사진을 보듯이 크로아티아 알파벳에는 Q,W,X,Y가 없기 때문에 이 문제는 다시 정의되어야 한다.

하지만 우리는 크로아티아어를 공부하는 목적이 아니므로 그냥 문제에 집중하면

대충 내가 생각한 문제 풀기 시나리오는…

  1. 변수를 하나 만들고 ‘조합’된 문자열들을 파이썬 리스트에 저장한다.
  2. 또 역시 변수를 하나 만들고 문자열 입력을 받고 저장한다.
  3. 리스트에 있는 문자들이 입력받은 문자열 속에 있는지 비교한다.(for 문, if 문, in 구문 사용)
  4. 만약 해당하는 문자열이 있을 경우 다른 length가 한 칸인 문자열로 바꾸고(나는 공백 “ “으로 했다) 2.에서 만든 변수에 저장한다.
  5. 2.에서 만든 변수에 담겨있는 문자열의 length를 출력한다.(그냥 알파벳은 length를 한 칸 차지하므로 조합된 알파벳?을 한 칸짜리로 만들면 결국 그냥 알파벳이랑 같아져서 총 length만 세면 된다)

이 시나리오대로 코드를 짜면 이렇게 짤 수 있다.

cr_alphabets = ['c=','c-','dz=','d-','lj','nj','s=','z=']
inputstr = input()
for i in cr_alphabets:
    if i in inputstr:
        inputstr = inputstr.replace(i," ")
print(len(inputstr))

코드는 잘 짜였는지 모르겠지만 잘 돌아간다. BOJ에 제출을 하니

역시 맞았습니다! 가 날 반겨 주었다.

replace() 문자열 메소드🔠

추가적으로 설명을 하자면 내가 모르고 있었던 replace() 문자열 메소드인데 공식문서를 살펴보면

str.replace(old, new[, count])

Return a copy of the string with all occurrences of substring old replaced by new. If the optional argument count is given, only the first count occurrences are replaced.

나의 짧고 어색한 영어로 설명해보자면 이 replace 메소드는

“원래 문자열 안에 있는 new (old를 바꿀 문자열)로 바뀐 old 라는 문자열(바뀔 문자열)의 모든 실재?들로 문자열의 복사본을 반환한다. 만약 선택적인 인수 count 가 주어진다면 첫 번째? count 실재?들만 바뀌게 된다.”

역시 영어는 어렵다. 무슨 말인지 이해가 안 된다. 코드로 돌려보자!

먼저 문자열

"돼지홍 돼지홍 돼지홍"

이 주어진다고 하자, 나는 돼지?홍이 아니기 때문에 저기 있는 “돼”를 “김”으로 바꾸고 싶다. 그러기 위해서

"돼지홍 돼지홍 돼지홍".replace("돼","김")

로 하면 되는데 위에 코드에서 old 인수 자리에는 원래 문자열 안에 있는 바꿀 문자열인 “돼” 가 오고 new 인수 자리에는 바뀔 문자열인 “김”이 된다. 이렇게 되면

"김지홍 김지홍 김지홍"

을 반환 하게 된다. 그런데 count 인수가 지금까지는 주어지지 않았다. 주어지게 되면 어떻게 될까?

"돼지홍 돼지홍 돼지홍".replace("돼","김",1)
"김지홍 돼지홍 돼지홍"
"돼지홍 돼지홍 돼지홍".replace("돼","김",2)
"김지홍 김지홍 돼지홍"
"돼지홍 돼지홍 돼지홍".replace("돼","김",3)
"김지홍 김지홍 김지홍"

그렇다. 정수인 count 인수가 주어지게 되면 문자열의 처음부터 count 인수로 들어온 값만큼의 old 인수의 문자열을 new 인수의 문자열로 바꾸는 것이다!

count 인수는 문자열의 처음부터 바뀔 문자열의 개수를 의미한다. (주어지지 않았을 때는 모든 문자열을 바꾼다)

만들어봐요🧶 replace() 함수

그래서 내가 구현하지 못했었던 replace() 메소드 대신 함수를 만들어 보자!

대충 내가 생각한 아이디어는

replace(string,old,new[,count]) 이고

count 인수를 받을 때는 기본값을 줘서 인수를 주지 않으면 모든 문자열을 바꾸게 할 생각이다.

내가 생각한 알고리즘은

  1. 함수의 인수(string,old,new[,count])값을 받는다.
  2. string 값을 copiedstr 변수에 넣는다.
  3. 바뀐 문자열들이 들어갈 리스트를 만들고 그 안에 string(바뀌지 않은 문자열)값을 넣어준다. (count 값을 리스트의 인덱스로 활용해서 불러올 생각)
  4. 루프를 만들고 그 안에 만약 old 값이 copiedstr 안에 있으면 문자열 인덱스를 이용해 바꿔주고 3.에서 만들어준 리스트에 넣어주고 만약 없으면, 루프를 탈출하게 한다.
  5. 만약 count 값이 기본값과 같으면 3.에서 만든 리스트에 마지막에 있는 즉, 모든 문자열이 바뀐 문자열을 반환한다. 아니면, count를 인덱스로 이용하여 리스트에 저장되어있는 값을 반환한다.

사실 위에 알고리즘은 코드를 짜면서 생각 한 것이기 때문에 이 글을 읽는 사람에게는 이상하게 느껴질 수 있다.

답은 간단하다. 내가 만든 것 보다 더 나은 자신만의 개성 있는 코드와 알고리즘을 만들면 된다!

아무튼 코드를 대충 이런 식으로 짤 수가 있겠다.

def replace(string,old,new,count="default"):
    copiedstr = string
    replacedlist = [string]
    while(True):
        if old in copiedstr:
            i_oldStart = copiedstr.index(old)
            i_oldEnd= i_oldStart + len(old)
            copiedstr = copiedstr[:i_oldStart] + new + copiedstr[i_oldEnd:]
            replacedlist.append(copiedstr)
        else:
            break
    if count == "default":
        return replacedlist[-1]
    else:
        return replacedlist[count]

역시 코드가 잘 짜여진것은 모르겠지만 역시 작동은 한다.

돼지홍을 김지홍으로 바꾸기도 성공했고, Boj 문제도 성공했다.

하지만 생각보다 쉽지는 않았다. 웬만해서는 있는 replace() 문자열 메소드를 쓰는 것이 정신건강에 좋을 것 같다.

다른 엔딩😮

사실 이 위에 있는 두 경우는 내가 처음으로 풀기 성공한 방법이 아니다.

예전에 수능특강 pdf 파일을 단어에 네이버 사전 링크를 건 html 문서로 만들때 “크롤링”을 사용한 적이 있었다.

그 과정에서 크롤링을 할 때 정규표현식을 사용하면 더 효율적으로 크롤링을 할 수 있다는 글을 어디서 보고 시도했다가 내용의 난이도가 조금 높아서 배우는 것을 잠시 뒤로 했었다.

하지만 문자열을 처리하는데 정규표현식을 사용하면 아주 효율적이기 때문에 이번 기회에 사용하게 되었다.

정규표현식에 자세한 대한 설명은 점프투파이썬: 정규표현식 에서 보는 것이 좋을 것 같다.

정규표현식은 “특수문자를 이용해서 패턴을 만들고 이 패턴을 이용해서 문자열이 이 패턴에 맞는지 확인할 수 있게 해준다”라고 나는 이해했다.

그래서 코드를 짜보았고 이상하게? 마법처럼 작동되었다.

import re
p = re.compile("c=|c-|dz=|d-|lj|nj|s=|z=")
result = p.sub(" ",input())
print(len(result))

코드는 정말 간단하다. 일단 파이썬 정규표현식 라이브러리인 re를 가져오고 re.complie()의 인스턴스인 p를 만든다.

그러고 나서 보면 re.compile()안에 이상한 문자열이 들어 있는 게 보일 것 이다. 이 문자열이 정규표현식이다.

이 정규표현식의 뜻은 c=,c-,dz=,d-,lj,nj,s=,z=중에 하나라도 문자열 안에 있으면 match(일치?)가 된다는 소리다.

그 다음 줄의 p.sub(“ “, input()) 부분은 우리가 사용했던 replace() 메소드와 비슷한데, sub() 메소드는 input()을 이용해 문자열을 입력받고 위의 정규표현식과 match가 되면 match 된 부분을 바뀌게 될 새로운 문자열인 “ “로 바뀐 값을 반환한다. 그리고 그 다음부터는 처음의 예시와 같다.

정규표현식은 처음에 배우기에는 확실히 어렵다.

하지만 한번 봐두면 (특정한 규칙이 있는)문자열 처리가 매우 쉬워진다.

정말 끝! 그리고 마치는 말🥳

지루한? 파이썬과 약간의 알고리즘?에 대한 글이 드디어 끝이 났다. 여기까지 읽은 당신에게 박수를 보낸다.👏

이 글을 쓰면서 아직 나의 프로그래밍 수준이 많이 낮다는 것을 깨닫고 많은 문제를 해결하면서 기본기를 더 잘 갖춰야 겠다는 생각을 하게 되었다.

아무튼 저번글에서 최대 일주일에 2개의 글이 올라간다고 했었는데, 생각보다 잘 되어지지 않았고 오히려 무조건 글을 써 올려야 한다는 생각때문에 글이 잘 써지지 않았기 때문에 그냥 앞으로 글을 조금 더 자주 쓰도록 하겠다.

지난 약 2개월의 시간 동안 작은 프로젝트를 하나 진행했고 지금은 리액트로 프로젝트를 진행하던 중 나의 자바스크립트에 대한 이해가 부족하다는 것을 깨닫고 새로운 프로젝트를 진행 중이다.

그래서 현재 진행하고 있는 프로젝트가 끝나면 짤막하게 개발 후기?와 사람들의 반응을 글로 써보도록 하겠다.

그때까지 기다려 주면 좋겠다. 그럼 안녕🙋‍♂️

참고 자료📄

https://en.wikipedia.org/wiki/Gaj%27s_Latin_alphabet

https://www.acmicpc.net/problem/2941

https://wikidocs.net/4308

https://docs.python.org/3/library/stdtypes.html#str.replace

https://python.bakyeono.net/chapter-3-5.html