220803_TIL / Problem Solving

신두다·2022년 8월 3일
0

TIL

목록 보기
66/82

Key words

문제해결, 의사코드(슈도코드), 컴프리헨션(comprehension), 예외 처리

오늘도 역시 개념 자체를 많이 배운다니보단 여러 상황에 대해 코드를 적고 돌려보는 시간이 많았다. 이번 섹션은 계속 이러려나보다.

기억하자!!

  • section5 전체의 최종목적은 자료구조와 알고리즘을 이해하며 프로그래밍하는 것이다.
  • section5의 핵심 키워드 : 문제해결과 컴퓨팅 사고력

1. 의사코드 (pseudocode, 슈도코드)

  • 슈도코드는 오늘 처음 들어봤다. 실제 코드를 쓰기 전에 어떤 구조로 쓸지 내가 이해할 수 있는 언어로 적어보는 걸 말한다.
  • 예를 들어 아래와 같은 것이다. (과제하면서 써본 것이다.)
list = [원소, 원소, 원소 ..]

max_num = 0 (리스트 원소 하나식 꺼내서 얘랑 비교하고 크면 이 변수값을 바꾸자)
i = 0 (인덱스)

while 반복문 i가 리스트 전체 길이보다 작으면 계속 반복: 
    if 리스트 i번째 원소가 max_num보다 크니?:
        max_num = list[i] # 그럼 갱신해.
        i += 1 (i 하나 더해)
    else: # 아님 갱신하지말고 넘어가
        i +=1 (i 하나 더해서 다음 원소도 보자)
  • 코딩을 하다보면, 아니지 아니지. 정확히는 문제 해결을 위해 코딩을 활용하다보면 논리적인 사고가 얼마나 중요한지를 자주 느낀다. 로직을 짠다는 것은 결국 내가 풀고자 하는 문제를 정의하고, 잘게 쪼개고, 그에 맞는 해결책을 제시한다는 것과 같기 때문이다.
  • 문제를 풀 때 머릿속이건 손으로건 여하간 어떤 로직으로 짤지 필연적으로 고민하게 되는데 그때 슈도코드라는게 있구나~ 정도로 알고 있음 될 듯 싶다.

2. 컴프리헨션(comprehension)

  • for문으로는 여러 줄에 써야하는 걸 한 줄로 쓸 수 있도록 하는 기능이다.
  • 한번의 for, if문 정도 쓸 수 있겠지 싶었는데, 중첩도 가능했었다. 아래는 참고하자.
# List Comprehension
[반복실행문 for 반복변수 in 반복범위]
[반복실행문 for 반복변수 in 반복범위 if]

# Set Comprehension
{반복실행문 for 반복변수 in 반복범위}
{반복실행문 for 반복변수 in 반복범위 if}

# Dictionary Comprehension
{:밸류 for 반복변수 in 반복범위}
{:밸류 for 반복변수 in 반복범위 if}

--
# 이중 for문

[반복실행문 for 반복변수 in 반복범위 for 반복변수 in 반복범위]
[반복실행문(if문 통과시) if 조건 else 반복실행문(else문 통과시) for 반복변수 in 반복범위]
  • 컴프리헨션 사용과 관련해서 유의해야할 점이 있는데 그건 바로 가독성 측면이다. 중첩이 한 번이라도 이루어지면 가독성이 팍! 떨어지기게 된다. 그래서 컴프리헨션은 실무에서 아주 기본적인 위 형태 정도로만 쓴다고 한다.
  • 이건 관련해서 내가 코치님께 했던 질문과 답이다.
    • 일반적으로 for loop보다 리스트 컴프리헨션이 속도가 더 빠르다고 들었는데, 오늘 노트에서 컴프리헨션에 대해 ‘조건문에 따라 메모리에 영향을 줄 수 있다'고 나와있더라고요. 그럼에도 속도 하나만 봤을 때는 조건문이 복잡해져도 for loop보다 일반적으로 빠른 것이 맞나요? 조건이 복잡해서 가독성을 좀 포기해도 속도가 더 중요한 경우엔 컴프리헨션을 선택할 수도 있나 해서요.
      • 협업할 때 가독성이 너무 중요하기 때문에 실무에서 이중포문 이상은 컴프리헨션을 거의 쓰지 않는다고 함. 일반적으론 컴프리헨션이 for loop에 비해 빠른건 맞는게, 저울질을 해봤을 때, 우선순위를 보았을 땐 가독성을 더 높이는게 맞다고 함.

3. 지역변수와 전역변수

코드를 짜다보면 수많은 변수를 정의하고 가져다 쓰고 하게 되는데, 그때 아래 개념을 알아두면 가독성, side-effect 방지 등 여러모로 이득을 볼 수 있을 것 같다.

  • 지역변수: 해당 변수가 포함된 함수 안에서만 사용 가능.
    • 같은 이름이라도 포함된 함수가 다르면 다른 변수로 취급됨.
  • 일반 전역변수: 하나의 파이썬 파일 전체에서 값을 읽을 수 있다. 되도록이면 이 값을 수정하지는 않고 쓴다고 함.
    • 어디서 괜히 수정되도록 되어있으면 이 값이 실제 뭔 값인지 헷갈릴거니까. 또 어떤 문제가 생길 수도 있고.
    • global 전역변수: 일반 전역 변수와 다른 건 변수가 생성되는 시점! (함수 내에서 선언되는 걸로 생각해도 될듯..?)

4. 예외 처리

  • 오늘 예외처리에 대해서도 보았는데, 오전의 warm-up영상에서도 그렇고 except 처리하는 것을 남용하지 말고 신중히 사용하라는 말을 자주 들었다. 나중에 디버깅하기도 힘들다고.
  • 근데 그럼에도 대규모 서비스에서 공동작업을 하는 경우 예외처리를 하는 것이 중요하다고 하니.. 결국 핵심은 웜업 영상에서 본 것처럼 내가 작성하는 코드를 잘 이해하고 있으며, '이 코드가 만약 실패하면 어떻게 될까?'도 생각해보면서 코딩을 하는 것인 것 같다.

근데 이런 식의 구문은 처음봤다.

for i in range(3):
    print('loop : ', i) 
else: 
    print('break가 실행되지 않았습니다.')
    
'''
[출력 결과]

loop :  0
loop :  1
loop :  2
break가 실행되지 않았습니다.
'''
  • 앞의 반복문에 break가 없으면 else문에 적힌 구문을 추가로 수행한다.
    • 만약 print('loop : ', i) 뒤에 break를 적어주면 출력은 loop : 0만 되고 끝난다.

아래 구문에서 나는 pass를 선호해왔는데, continue를 쓸 수도 있다. 해당 조건은 건너뛰고 다시 반복문을 수행한다는 거다.

# 오이를 싫어하는 사람의 김밥
bucket = ["햄", "계란", "참치","당근", "우엉", "오이", "단무지"]
gimbab = list()

for ingredient in bucket:
    if ingredient == "오이":
        continue
    else:
        gimbab.append(ingredient)

print(gimbab)

# ['햄', '계란', '참치', '당근', '우엉', '단무지']

5. 그 외

  • try ~ except ~ else : 참고로 마지막에 finally를 쓰면 무조건 실행된다. 아래 코드도 내가 자주 짜본 내용은 아니니 잘 기억해두면 좋을 듯.
    • 앞에서 for ~ else 때 봤던 것처럼 else문은 try문이 실행되면 그 다음에 작동한다. 뒤에 finally는 else 다음에 무조건 작동된다는 거고.
def disneyland():
    try:
	    age = int(input("나이를 입력해주세요: "))
    except ValueError as e:
        print("숫자로 나이를 입력해주세요.")
        print(e)
    else:
        # 3세 미만은 공짜
        if age < 3:
            fee = 0
        # 3세~9세: 50,000
        elif age >= 3 and age < 10:
            fee = 50000
        else:
            fee = 100000
        return fee
    finally:
        print("디즈니 랜드에 오신 것을 환영합니다~!")

disneyland()
  • assert 문: 우리 pytest할 때 자주 봤던 거다. 적재적소에 심어두면 디버깅할 때 유용할 것 같다.
    • assert 조건식, 조건식이 false일 경우 출력 메시지인 것 기억! AssertionError
  • 아래 문제에 대해서 내가 풀었던 답은 다음과 같다.
"""
아래의 test 딕셔너리를 사용해서 아래처럼 출력되도록 만드시오. 컴프리헨션으로 푸는 문제가 아닙니다!
test = {'A': 5, 1: 'B', 'C': 9, 'D': 6, 5: 'E', 'F': 'G', 3:9} 

1) 키는 문자여야 한다.
2) 밸류는 숫자여야 한다.
3) 키가 숫자이고 밸류가 문자인 경우 반대로 값을 넣어준다.

#문제 더 어렵게 하면 숫자를 float 케이스 넣을 수 있음

result = {'A': 5, 'B': 1, 'C': 9, 'D': 6, 'E': 5}
"""

test = {'A': 5, 1: 'B', 'C': 9, 'D': 6, 5: 'E', 'F': 'G', 3:9}

result = {}

for i in range(len(test)):
  if type(list(test.keys())[i]) == str and type(list(test.values())[i]) == int:
    result[list(test.keys())[i]] = list(test.values())[i]
  elif type(list(test.keys())[i]) == int and type(list(test.values())[i]) == str:
    result[list(test.values())[i]] = list(test.keys())[i]
  else:
    pass

result
  • 근데 isintance()를 이용하는 방법도 있다고 함.
# 위 문제는 이렇게도 할 수 있다고 함! 
test = {'A': 5, 1: 'B', 'C': 9, 'D': 6, 5: 'E', 'F': 'G', 3:9}
result = dict()
for a, b in test.items():
    if type(a) != type(b):
        if isinstance(b, str):
            result[b] = a
        else:
            result[a] = b

print(result)
  • 그리고 또 재밌었던 건 만약 숫자가 문자형으로 들어가 있는데 그걸 숫자로 보고 제외해야하는 경우이다. 예를 들면 '3':9 식으로 되어있으면 위 함수로는 못 제낀다! 그럴 땐 try except 문으로 int 변환시 에러가 나면 ~~ 아니면 ~~ 식으로 구성하면 된다.

6. 실습한 것

오늘은 아래 과제를 했다.

[part1 - 컴프리헨션]

"""
요구사항:
    리스트 컴프리헨션 개념을 적용하여 '1~100까지 7과 5의 공배수' 를 구하는 코드를 작성하세요.
    아래 예시입력값과 출력값을 참조하며 문제를 해결해봅니다.

    입력값:
        없음
    출력값:
        [35, 70]
"""

def part1():

    res = [i for i in range(1,101) if i % 7 == 0 and i % 5 ==0]

    return res

'''
[기록] - list comprehension을 안 쓰면 이렇게도 할 수 있다!

def part1():

    res = []

    i = 1

    while i <= 100:
        if i % 7 == 0 and i % 5 ==0:
            res.append(i)
            i +=1
        else:
            i +=1

    return res

'''

[part2]

"""
    '리스트의 값 중 최댓값'을 구하도록 코드를 구현하세요.
    파이썬의 내장함수를 사용하지 않고, 반복문과 조건문을 활용합니다.
    아래 예시입력값과 출력값을 참조하며 문제를 해결해봅니다.

    입력값:
        [4, 8, 5, 11, 7, 2]
    출력값:
        11
"""

def part2(num):
    max_num = 0 

    i = 0 

    while i < len(num): # 그냥 for문 써도 똑같음 => 'for i in range(len(ls)):'
        if num[i] > max_num:
            max_num = num[i]
            i += 1
        else: i += 1

    return max_num

'''
[기록]

# 기본 아이디어 == 그냥 하나씩 꺼내서 비교하자.

list = [원소, 원소, 원소 ..]

max_num = 0 # 리스트 원소 하나식 꺼내서 얘랑 비교하고 크면 이 변수값을 바꾸자.
i = 0 # 인덱스

while i < len(list): # 그냥 for 문 
    if list[i] > max_num: # 꺼내온 값이 max_num 보다 높니? 
        max_num = list[i] # 그럼 갱신해.
        i += 1
    else: # 아님 갱신하지말고 넘어가
        i +=1 
    
이런 식으로 해보면 될듯?
'''
  • 음.. 처음에 아이디어 생각해내기까지 좀 헤맸다. part3도 그렇고 이런 류의 문제는 외부 변수에 특정 조건 만족시 카운트해나가는 식으로 푸는 방법을 많이 보아왔던 것 같다. 경험칙 정도로 기억하면 좋을 듯.

[part3]

"""
-1부터 1이상의 입력받은 숫자까지 모든 숫자에 대해 소수가 몇 개 있는지 반환하세요.
-0이하의 경우 ValueError를 발생시켜 주세요.
"""

def part3(N):
    if N>1:
        pn_list = [] # 소수를 넣어줄 리스트

        ls = [i for i in range(2, N+1)] # 2부터 N까지의 숫자가 들어간 리스트

        for i in range(len(ls)): # ls에서 하나씩 꺼내서 각 원소별로 그게 소수인지 아닌지 볼 것이다.
            
            num = ls[i] 

            ls2 = [a for a in range(2, num+1)] # 뽑은 원소까지의 리스트

            count = 0 # 아래 for loop 돌고 난 후 count = 1이어야 소수다.

            for n in ls2:
                
                if num % n == 0: # num을 또 ls2에서 뽑은 원소로 나눌 때 나머지가 0인 경우 카운트. 
                    count += 1
                else: pass

            if count == 1:
                pn_list.append(n)
            else: pass
            
    else: raise ValueError

    return len(pn_list)

'''
[기록]

- '소수'가 뭔지에 대한 정의부터 내려야 함: '1과 자기 자신 외의 약수를 가지지 않는 1보다 큰 자연수'
    => 이걸 어떻게 코드로 표현할 수 있을까? 
    => 일단 짝수는 2 빼고는 전부 소수 아님. (= 2로 나눠서 나머지 0이면 날리면 됨.)
        => 일의 자리 수에 이렇게 규칙만들기 거슬리는 애들 있으니 십의자리부터 고려하고 일의자리 소수는 아예 고정해두자.
    => 홀수는 구구단의 홀수단 3, 5, 7, 9로 나누어 나머지가 0이면 다 날리면 될 듯? 

def part3(N):


    if N >=10:
        ls = [2,3,5,7] # 소수 담을 공간. 10개 이상인 경우 일의 자리는 기본으로 담기도록.
        for i in range(2,N+1):
            if i % 2 != 0 and i % 3 != 0 and i % 5 != 0 and i % 7 != 0 and i % 9 != 0:
                ls.append(i)
            else: pass
        num_pn = len(ls)
    else: # 좀 더 간단히 할 방법은 없을까..?
        if N >= 7:
            num_pn = 4
        elif N >= 5:
            num_pn = 3
        elif N >= 3:
            num_pn = 2
        elif N >=2:
            num_pn = 1
        else: raise ValueError

    return num_pn

=> 음.. 아니네. 위같이 하면 100까지는 되는데 그 이상의 수는 예를 들면 11*11 = 121도 소수로 잡히는 문제가 있다. 

- 아예 그냥 '소수' 원래 정의대로 돌아가서 해보자. 
    => 2부터 자기 숫자까지 리스트 만들고 하나씩 뽑아서 자기 자신에 나눠보고 나머지가 0이면 카운트 +1, 
    => 그리고 마지막에 카운트가 2이면 소수로 추가하는 식으로 하자.
'''
  • 이거 예전에 파이썬 공부하면서 한 번 풀어봤던 문제 같은데..헤맸다!
  • 다 제출하고 나서 동기들 코드 보니까 나처럼 리스트를 하나 더 안 만들고(내 코드에선 ls2) range로 하던데 그게 더 깔끔한 것 같다. 똑같은 기능인데 이렇게 코드가 간단해진다.
def part3(N):
    if N <= 0:
        raise ValueError

    n = 0
    for i in range(2, N+1):
        k = 0
        for j in range(2, i):
            if i % j == 0:
                k += 1
                break
        if k == 0:
            n += 1
    return n
Footer
  • 두번째 for loop에서는 range end를 자기 자신으로 해서 그 이전에 나뉘는게 있으면 break하는 방식이다. 하~ 나도 이렇게 깔끔하게 적고 싶다! 코드 더 많이 적어봐야겠다. 머릿속에 구조만 잘 짜두면 된다고!

Feeling


  • 날씨가 흐려서 그런지 잠을 잘 못자서 그런지 피곤하고 몸이 좀 쳐진다.
  • 그래도 파이썬은 재밌다. 오늘은 주로 문제가 주어지면 푸는 걸 했는데 잘 풀면 또 재밌다. 오늘과 같은 건 예전에도 많이 재밌게 해봤던거라 익숙.
  • 빨리 9월이 되었으면 좋겠다..ㅠㅠ
profile
B2B SaaS 회사에서 Data Analyst로 일하고 있습니다.

0개의 댓글