오늘은 파이썬 심화프로그래밍 마지막날이었습니다.
주요한 진도는 다 진행이 되었기 때문에 여러 코딩테스트용 문제풀이를 진행했습니다.
문제풀이를 하면서 어떻게 해결할까 떠올려 보니 자연스럽게 복습이 됐습니다.
프로그래머스나 파이알고에 올라와 있는 코딩테스트 문제들이 생각보다 어려웠습니다.
좀 더 다양한 문제들을 접하고 많이 풀어보는 것이 복습에도 더 도움이 된다고 느꼈습니다.
문제들과 주요 코드들을 정리해보겠습니다.


이메일 주소 변경

# 문제: 주어진 문자열에서 모든 이메일 주소를 user@weniv.co.kr으로 변경하시오.

# 예시 입력: "저의 이메일 주소는 kim123@gmail.com입니다. 친구의 이메일 주소는 lee456@gmail.com입니다."
# 예시 출력: "저의 이메일 주소는 kim123@weniv.co.kr입니다. 친구의 이메일 주소는 lee456@weniv.co.kr입니다."

my_string = "저의 이메일 주소는 kim123@gmail.com입니다. 친구의 이메일 주소는 lee456@gmail.com입니다."
re.sub(r"@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}", "@weniv.co.kr", my_string)

이 문제의 핵심은 정규표현식입니다.
문제 자체에서 정규표현식을 알려줬지만 한번 분석을 해봤습니다.
예시 입력 문장에서 이메일 주소만 확인해서 @weniv.co.kr 으로 변경해야 합니다.
@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}
여기서 []는 택일 하라는 것입니다.
대문자A-Z, 소문자a-z, 숫자0-9, ., - 까지 허용합니다.
[] 뒤에 +는 앞에 있는 문자가 1~n개 까지 올 수 있다는 의미입니다.
\. 에서 \. 을 표현하기 위해 이스케이프 문자표현입니다.
그리고 . 아래 있는 []는 택일 이고, A-Z|a-z 는 대문자A-Z 또는 소문자a-z 가 올 수 있다는 의미입니다.
그 뒤에 {2,} 는 앞에 있는 문자가 2개 이상 이라는 의미입니다.
복잡하시만 가능한 이메일이 어떤 형태인지 정리한 정규표현식입니다.


모음을 제거한 문자열

# 입력 받은 숫자 리스트에서 짝수의 개수와 홀수의 개수를 반환하는 리스트를 출력
# 입력: [1, 2, 3, 4, 5], 출력: [2, 3]
# 입력: [1, 3, 5, 7]     출력: [0, 4]
data = [1, 2, 3, 4, 5]

def solution(data):
    홀수 = [i % 2 != 0 for i in data]
    짝수 = [i % 2 == 0 for i in data]

    return [sum(짝수), sum(홀수)]

solution(data)

리스트를 for문 돌려서 2로 나눴을때 0이 아닌건 홀수 리스트에 넣고, 2로 나눴을때 0인건 짝수 리스트에 넣습니다.
그 값들을 sum해서 return하면 같은 결과를 얻을 수 있습니다.

# 입력 받은 숫자 리스트에서 짝수의 개수와 홀수의 개수를 반환하는 리스트를 출력
# 입력: [1, 2, 3, 4, 5], 출력: [2, 3]
# 입력: [1, 3, 5, 7]     출력: [0, 4]
data = [1, 2, 3, 4, 5]

def solution(num_list):
    answer = [0, 0]
    for i in num_list:
        answer[i % 2] += 1
    return answer

solution(data)

미리 return할 리스트 answer를 만들어 놓습니다.
매개변수로 받은 num_list를 for문으로 돌려서 answer[i % 2] += 1 구문을 실행합니다.
리스트를 i % 2 하면 결과가 0 아니면 1이 나와서 answer[0], answer[1] 만 됩니다.
거기에 값을 1씩 추가해주면 리스트에서 짝수의 개수와 홀수의 개수가 자연스럽게 정리되는 겁니다.
멋진 코드에요. 이렇게 깔끔하게 코딩을 할 수 있을까요?


키 큰 사람 출력

# 리스트와 기준 숫자를 입력을 받고, 리스트에서 기준 숫자보다 큰 값의 갯수를 출력
#           입 력                  출력
# [149, 180, 192, 170]  167         3
# [180, 120, 140]       190            0
data = [149, 180, 192, 170]
n = 167

def solution(array, height):
    answer = 0
    for i in array:
        if i > height:
            answer += 1
    return answer

solution(data, n)

기본적으로 생각할 수 있는 코드입니다.
for문을 입력받은 리스트만큼 반복하고, 각 값 하나하나가 기준키 보다 큰지 체크해서 큰 경우만 answer에 1씩
추가하는 코드입니다.

# 리스트와 기준 숫자를 입력을 받고, 리스트에서 기준 숫자보다 큰 값의 갯수를 출력
#           입 력                  출력
# [149, 180, 192, 170]  167         3
# [180, 120, 140]       190            0
data = [149, 180, 192, 170]
n = 167

def solution(array, height):
    return len(list(filter(lambda x: x > height, array)))

solution(data, n)

filter함수를 이용한 출력입니다. filter는 리스트에서 조건에 맞는 값들만 골라내는 함수입니다.
filter함수에는 len이 없습니다. 그 이유는 filter는 순회 돌기 전까지 실행이 되지 않는 함수입니다.
for와 같이 순회를 돌아야 그때서야 각 원소 별로 접근합니다.
그래서 list로 만들어주고, len을 통해 원소의 갯수를 return할 수 있습니다.


숨어있는 숫자의 출력

# 문자열에서 소문자, 대문자 제외한 한자리 자연수만 뽑아서 숫자 더해서 출력
data = "aAb1B2cC34oOp"
# data = "1a2b3c4d123"

def solution(my_string):
    answer = 0
    for i in my_string:
        if i.isdigit():
            answer += int(i)
    return answer

solution(data)

문자열을 입력 받고, for문으로 순회해서 문자열 앞에서 하나씩 숫자인지 isdigit() 으로 판단하고, 숫자인 경우
int로 형변환해서 더해줍니다. 문자열 3은 더한 값을 구할 수 없습니다.

r'hello\nworld' # \n은 이스케이프 문자입니다.
print('hello\nworld')

import re

def solution(my_string):
    return sum(map(int, re.findall(r'[0-9]', my_string)))
solution("aAb1B2cC34oOp")

간단한 샘플입니다. "aAb1B2cC34oOp" 문자열을 매개변수로 보내고, 문자열에서 0~9까지 숫자를 찾고,
int형으로 형변환 하고, sum을 해주고 return하는 코드입니다.
그외 간단하게 여러 용어들을 설명해주셨습니다.
r은 raw 날것의 라는 뜻이 있습니다. 실무에서 자주 쓰는 용어이고, raw != low 는 다릅니다.
raw data를 전달해줘 라는 말은 가공하지 않은 순수데이터를 달라는 얘기입니다.
print(r'hello\nworld') 여기서 \n은 줄바꿈 이스케이프 문자입니다.


암호해독

# "dfjardstddetckdaccccdegk" 을 입력받고, 4를 입력받아 문자열에서 4칸 단위로 문자를 뽑아서 출력
# "pfqallllabwaoclk"을 입력 받고, 4를 입력 받아 문자열에서 2칸 단위로 문자를 뽑아서 출력
data = "dfjardstddetckdaccccdegk"
n = 4
# data = "pfqallllabwaoclk"
# n = 2

def solution(cipher, code):
    count = 1
    answer = ''
    for i in cipher:
        if count == code:
            count = 0
            answer += i
        count += 1
    return answer

solution(data, n)

입력 받은 문자열을 for문으로 순회를 돌고, if문에서 자리값이 code와 같은지 확인하고, 같은 경우 count를
0으로 초기화 하고, answer에 그 문자를 추가합니다. 0으로 초기화된 카운트는 if count == code 에서
4가 될때 까지 skip 합니다. 결국 count가 4일때 더해진 문자들을 더해서 출력하면 원하는 결과값을 얻습니다.

data = "dfjardstddetckdaccccdegk"
n = 4
# data = "pfqallllabwaoclk"
# n = 2

def solution(cipher, code):
    return cipher[code-1::code]

solution(data, n)

문자열 슬라이싱을 이용하는 코드입니다. 슬라이싱의 자리 숫자들의 의미는 [start index: end index: step] 입니다.
[::code] 했으면 문자열의 첫번째 자리부터 4자리 단위로 잘라서 return 했을 것입니다.
code-1 해서 index 자리를 하나 앞으로 세팅해서 조정했습니다. 정말 멋진 코드입니다.
이렇게 간단하게 처리할 수 있습니다.


369게임

# 숫자를 입력 받고, 그 숫자에서 3, 6, 9의 갯수 출력
# 3을 입력 받으면 1
# 29423 을 입력 받으면 2

# data = 3
data = 29423

import re

def solution(order):
    answer = 0
    order = str(order)
    answer = len((list(re.findall(r'[369]', order))))
    return answer

solution(data)

입력받은 숫자에서 369가 들어간 문자를 뽑아서 리스트로 만들고, 그 리스트의 len을 출력하는 코드입니다.
핵심은 정규표현식입니다. findall()은 원하는 걸 다 찾아내는 함수입니다.
정규표현식에서 []는 택일입니다. [369]는 숫자 369 가 들어간 것을 찾아냅니다.

# data = 3
data = 29423

import re

def solution(order):
    order = str(order)
    return order.count('3') + order.count('6') + order.count('9')

solution(data)

이렇게도 만들 수 있습니다. 입력받은 값을 문자열로 바꾸고, 문자열에서 order.count('3')는 3이 있는 갯수를
리턴합니다. 그렇게 3, 6, 9 가 있는 갯수를 더해서 값을 출력합니다.


중복문자 제거

# 입력 받은 문자열에서 중복된 문자를 제거하고, 중복된 문자는 하나의 문자만 남긴 문자열을 출력
# data = "people"
# data = "We are the world"

data = "We are the world"

def solution(my_string):
    answer = ''
    for i in my_string:
        if i not in answer:
            answer += i
    return answer

solution(data)    

문자열을 입력 받고, 문자열 만큼 for문으로 순회를 도는데 문자열의 문자가 answer에 있는지 없는지 확인하고,
없을 경우에 answer 에 그 문자를 추가합니다. answer에 그 문자가 있다면 추가하지 않기 때문에
중복된 문자를 추가하는 것을 피할 수 있습니다.

data = "We are the world"

def solution(my_string):
    return ''.join(dict.fromkeys(my_string)) # 딕셔너리는 키의 중복이 안된다는 점을 이용! dict는 이제 순서를 보장합니다!

solution(data)

딕셔너리는 키가 중복이 안된다는 점을 이용했습니다. 그리고 파이썬은 3.7 버전 이상부터는 딕셔너리가 순서를 보장합니다.
dict.fromkeys(my_string)my_string의 각 요소를 키로 하는 새로운 딕셔너리를 생성합니다. 딕셔너리의 특성상 각 키는
고유해야 해서 중복된 요소는 하나만 유지됩니다.
"We are the world" 문자열은 {'W': None, 'e': None, ' ': None, 'a': None, 'r': None, ...} 형태의 딕셔너리를 생성합니다.
그리고, ''.join()은 주어진 문자열을 사용해서 시퀀스의 요소들을 연결합니다. 여기서는 빈 문자열''을 사용해서
딕셔너리의 키들을 연결합니다.


A로 B 만들기

# before 와 after 문자열이 주어질 때 before의 순서를 바꾸어 after를 만들 수 있으면 1을 출력, 아니면 0을 출력
# before 는 "olleh" after는 "hello" 일때 결과는 1
# before 는 "allpe" after는 "apple" 일때 결과는 0

def solution(before, after):
    answer = 0
    if len(before) != len(after):
        return 0

    if sorted(before) == sorted(after):
        answer = 1
    return answer

solution("olleh", "hello")

before 와 after의 문자열 길이가 다르면 0을 출력하고, before 와 after를 정렬했을때 결과가 같으면 1을 출력 하는 코드입니다.

import collections

def solution(before, after):
    answer = 0

    if collections.Counter(before) == collections.Counter(after):
        answer = 1

    return answer

solution("olleh", "hello")

collection을 이용한 코드입니다. 코드에서 collections.Counter(before)collections.Counter(after) 부분은
각각 before 문자열과 after 문자열의 각 문자가 몇 번 나타나는지를 세어, 이를 Counter 객체로 return합니다.
collections.Counter("olleh")Counter({'o': 1, 'l': 2, 'e': 1, 'h': 1}) 을 return합니다.
collections.Counter("hello")Counter({'h': 1, 'e': 1, 'l': 2, 'o': 1}) 을 return합니다.
Counter 객체는 문자의 등장 순서는 고려하지 않고, 단순히 문자의 개수만을 카운트 하게 됩니다.
그래서 결과적으로 두 문자열이 문자들의 종류와 개수는 같지만 배열 순서는 다를 수 있는지 확인합니다.


강의 내내 여러문제를 풀었습니다. 다양한 문제를 풀었지만 오늘은 일단 여기까지 정리합니다.
추후에 나머지 문제들은 추가적으로 더 정리해 보도록 하겠습니다. 4문제 정도 빠졌네요.^^