Skip to content

[월요일] 5주차 구현 - 2 + 재귀 #10

@SuwonPabby

Description

@SuwonPabby

🔥 금요일 5주차 - 구현 2, 재귀 🔥

Contents

1. lambda 표현식 정리 (잘못 설명한 것 정정)
2. 재귀 쉽게 구현하기
3. sys.setrecursionlimit 함수 (암기 요망)
4. 입력 받으면서 island 정보를 리스트로 얻어내는 코드 - 지구온난화
5. Deque의 초기화 시간

1. lambda 표현식 정리 (잘못 설명한 것 정정)

  • 일단 1번 코드보다 2번 코드가 느린 것은 참입니다.
  • 1번 코드
    if a == 1:
      print(1)
  • 2번 코드
def is_a_one(a):
    return True if a == 1 else False

if is_a_one(a):
    print(1)
  • 즉 함수 호출이 존재한다면 함수를 불러오고 매개변수를 복사하고 실행시켜 리턴된 값을 가져오는 시간이 추가로 소요됩니다.
  • 즉 함수 호출이 너무 자주 일어난다면 호출이 없는 것에 비해 오버헤드가 발생할 수 있습니다.

그런데 지난번 설명에서 Lambda 표현식은 함수 호출이 안되고 인라인으로 실행된다고 말씀 드렸는데 이는 틀린 설명입니다.

람다 표현식도 똑같이 함수 호출이 일어납니다!

따라서 람다 표현식을 쓰나, 그냥 함수 호출을 쓰나 성능은 거의 같습니다.

  • 파이썬 블로그를 보면 가끔 람다 표현식을 인라인 함수라고 말하는 경우가 있습니다.
  • 하지만 이는 엄밀하게는 정확하지 않습니다.
  • 소위 인라인은 C/C++과 같은 컴파일 언어의 빌드 과정에서 인라인 처리된 함수부분이 전처리 때 그대로 코드 내로 들어가는 식으로 전처리 되어, 함수 호출이 안 일어나는 방식으로 코딩하는 것을 말합니다.
  • 그런데 파이썬에서는 람다 표현식 역시 똑같이 함수 호출이 일어납니다!! 따라서 엄일하게 임라인이 아닙니다.
  • 그냥 코드 내에다가 함수를 적는 것이 마치 인라인 느낌이 들어 그런 말을 하는 것에 불과합니다!

람다 표현식의 정확한 명칭은 익명 함수 (Annoymous Function) 입니다.

  • 람다 표현식은 일반 함수랑 똑같이 함수 호출을 해서 성능이 같은데 굳이 왜 쓸까요?
  • 저희가 길이가 짧은 로직까지 굳이 함수 이름을 작명하면서 코딩하기 불편할 것입니다.
  • 또한 함수로 따로 빼면 향후 코드를 읽어볼 때 함수를 찾으러 가야하므로 가독성을 해치게 됩니다.
  • 따라서 짧은 로직은 따로 이름을 정하지 않고 익명 함수 표현을 이용하는 것입니다.

2. 재귀 쉽게 구현하기

Base Case와 Recursive Case를 생각하며 코딩하기!

  • Base Case : 재귀가 끝나는 지점!
  • Recursive Case : 재귀가 계속 이어지는 지점!
    (단 꼭 if 문 등으로 명시적으로 Base Case다, Recursive Case다
    이런 것이 아닌, if 문 만족을 안 시켰을 때 Base Case 이런 식으로도 가능)
def factorial(n):
    # Base Case
    if n == 1:
        return 1
    #Recursive Case
    else:
        return factorial(n-1) * n
  • 희주님 코드
import sys

N = int(sys.stdin.readline().rstrip()) # 재귀 횟수 N

start_line = "어느 한 컴퓨터공학과 학생이 유명한 교수님을 찾아가 물었다."
question = "\"재귀함수가 뭔가요?\""
story_1 = "\"잘 들어보게. 옛날옛날 한 산 꼭대기에 이세상 모든 지식을 통달한 선인이 있었어."
story_2 = "마을 사람들은 모두 그 선인에게 수많은 질문을 했고, 모두 지혜롭게 대답해 주었지."
story_3 = "그의 답은 대부분 옳았다고 하네. 그런데 어느 날, 그 선인에게 한 선비가 찾아와서 물었어.\""

answer = "\"재귀함수는 자기 자신을 호출하는 함수라네\""
last_sentence = "라고 답변하였지."
tap ="____"

def recursive_function(n):
    print(tap*n+question) # 질문
    
    if n == N: # N 번째에
        print(tap*n+answer) # 답변
        print(tap*n+last_sentence) # 마무리 문장 출력 
        return 1
    
    print(tap*n+story_1) # 이야기 출력
    print(tap*n+story_2)
    print(tap*n+story_3)
    
    recursive_function(n+1) # n+1 재귀 호출
    
    print(tap*n+last_sentence) # 마무리 문장 출력 
    
print(start_line) # 첫 문장 
recursive_function(0) # 재귀 함수 호출

## 실행시간: 44ms

3. sys.setrecursionlimit (암기 요망)

  • 파이썬에서 재귀 호출은 그 횟수가 정해져 있습니다. (recursion limit이 존재)

  • 따라서 특정 횟수 이상으로 재귀 호출을 하게되면 Recursion Error 가 발생하게 됩니다.

  • 따라서 이를 방지하기 위해 아래와 같은 코드를 작성할 수 있습니다

    import sys
    sys.setrecursionlimit(10**5)
  • 이렇게 recursion limit 을 큰 숫자로 높일 수 있게 되어 Recursion Error가 발생하지 않습니다.

4. 입력 받으면서 island 정보를 리스트로 얻어내는 코드 - 지구온난화

  • 백준 Python 1위 코드 참고
import sys
input = sys.stdin.readline

r,c = map(int,input().split())

sea = []
land = []
for i in range(r):
    m = ['.'] + list(map(str,input().rstrip())) + ['.']
    sea.append(m)
    for j in range(c+2):
        if sea[i][j] == 'X':
            land.append([i+1,j])
sea = [['.'] * (c+2)] + sea + [['.'] * (c+2)]

new_land = []
result = []

while land:
    x,y = land.pop()
    check = 0
    for i,j in [x+1,y],[x-1,y],[x,y+1],[x,y-1]:
        if sea[i][j] == '.':
            check += 1
    if check >= 3:
        new_land.append([x,y])
    else:
        result.append([x,y])
else:
    for x,y in new_land:
        sea[x][y] = '.'

land_0 = sorted(result, key=lambda x: x[0])
land_1 = sorted(result, key=lambda x: x[1])
x = land_0[0][0]
y = land_0[-1][0]
a = land_1[0][1]
b = land_1[-1][1]
for i in range(x,y+1):
    print(''.join(sea[i][a:b+1]))
  • 이 코드는 island 값을 미리 받아 처리하고 있고, Sorting을 이용하여 처리하고 있습니다.
  • 이중 반복문을 한번 더 사용하면 시간 복잡도가 $O(RC)$ 가 되고, Sorting을 이용하면 잠기지 않은 섬의 개수가 $n$일 때 $O(nlogn)$ 의 시간복잡도가 발생합니다.
  • 물론 $R = 5\ C = 5\ n = 20$ 인 경우에는 Sorting이 시간이 더 걸리나, 일반적으로 가라앉지 않고 살아남은 섬의 개수가 작은 경우가 많기 때문에 시간이 적게 소요된 것으로 풀이됩니다.

5. Deque의 초기화 시간 (그런데 이것 정확한 실험 필요)

  • 대부분의 상황에서 Queue와 같이 popleft를 사용해야 할 때는 당연히 Deque을 선언해서 푸는 것이 맞습니다.

  • 또한 대부분의 상황에서 rotate라는 메서드 이용 (혹은 popleft한걸 다시 append) 하는 것을 필요로 할 때도 Deque을 선언해서 푸는 것이 맞습니다.

  • 그러나 이 경우 우리가 따지는 톱니가 4개 밖에 없기 때문에 $O(n)$ 이더라도 4번만 연산을 수행하면 되고 이는 유의미한 차이가 아닙니다.

  • 이 때는 Deque을 초기화 하는데 걸리는 시간이 상대적으로 큰 영향을 끼칠 수 있습니다.

  • 그래서 Deque을 초기화하지 않고 List 상태로 같은 코드를 수행하는게 이 문제에서는 더 빠릅니다.

  • 아니면 아예 String을 이용해 아래와 같이 코딩할 수도 있습니다.

    new_gear_state = gear_state[-1] + gear_state[:-1]
  • 따라서 이 문제에서는 Deque을 초기화 안하는 것이 더 빠른 풀이긴 했습니다.

  • 하지만 대부분의 상황에서는 Deque을 초기화한 이후 많은 연산을 수행하기 때문에 Deque이 대부분 빠르다고 보시면 됩니다.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions