파이썬에서 함수는 일급 객체이다. 즉 함수는 int나 list와 같은 모든 자료형들과 같이 객체로 취급할 수 있다는 뜻이다. 함수에도 자료형이 있기 때문에 예를 들어 type (fact)라는 수식을 입력하면 <type 'function'>이라는 결과가 나온다.
# 함수를 리스트의 요소로 사용하기
def applyToEach(L, f):
for i in range(len(L)):
L[i] = f(L[i])
def factR(n):
if n == 1:
return n
else:
return n*factR(n-1)
def fib(n):
if n == 0 or n ==1:
return 1
else:
return fib(n-1) + fib(n-2)
L = [1, -2, 3.33]
print('L =', L)
print('Apply abs to each element of L.')
applyToEach(L, abs) # 절대 값
print('L =', L)
print('Apply int ot each element of', L)
applyToEach(L, int)
print('L =', L)
print('Apply factorial to each element of', L)
applyToEach(L, factR)
print('L =', L)
print('Apply Fibonnaci to each element of', L)
applyToEach(L, fib)
print('L =', L)
함수 applyToEach는 함수로 된 인자를 가지고 있기 때문에 고차함수라 불린다 . 처음 호출되었을 때 applyToEach는 단항 내장 함수인 abs를 각 요소에 적용하여 리스트 L을 변형시킨다. 두 번째 호출되었을 때는 각 요소를 형식 변환한다. 세번째 호출되었을 때는 각 요소들에 정의된 factR 함수를 적용한다. 네 번째 호출되었을 때는 각 요소들에게 정의된 fib 함수를 적용한다.
출력 결과
L = [1, -2, 3.33]
Apply abs to each element of L.
L = [1, 2, 3.33]
Apply int ot each element of [1, 2, 3.33]
L = [1, 2, 3]
Apply factorial to each element of [1, 2, 3]
L = [1, 2, 6]
Apply Fibonnaci to each element of [1, 2, 6]
L = [1, 2, 13]
파이썬에서 함수는 일급 객체이다. 즉 함수는 int나 list와 같은 모든 자료형들과 같이 객체로 취급할 수 있다는 뜻이다. 함수에도 자료형이 있기 때문에 예를 들어 type (fact)라는 수식을 입력하면 <type 'function'>이라는 결과가 나온다.
# 함수를 리스트의 요소로 사용하기
def applyToEach(L, f):
for i in range(len(L)):
L[i] = f(L[i])
def factR(n):
if n == 1:
return n
else:
return n*factR(n-1)
def fib(n):
if n == 0 or n ==1:
return 1
else:
return fib(n-1) + fib(n-2)
L = [1, -2, 3.33]
print('L =', L)
print('Apply abs to each element of L.')
applyToEach(L, abs) # 절대 값
print('L =', L)
print('Apply int ot each element of', L)
applyToEach(L, int)
print('L =', L)
print('Apply factorial to each element of', L)
applyToEach(L, factR)
print('L =', L)
print('Apply Fibonnaci to each element of', L)
applyToEach(L, fib)
print('L =', L)
함수 applyToEach는 함수로 된 인자를 가지고 있기 때문에 고차함수라 불린다 . 처음 호출되었을 때 applyToEach는 단항 내장 함수인 abs를 각 요소에 적용하여 리스트 L을 변형시킨다. 두 번째 호출되었을 때는 각 요소를 형식 변환한다. 세번째 호출되었을 때는 각 요소들에 정의된 factR 함수를 적용한다. 네 번째 호출되었을 때는 각 요소들에게 정의된 fib 함수를 적용한다.
출력 결과
L = [1, -2, 3.33]
Apply abs to each element of L.
L = [1, 2, 3.33]
Apply int ot each element of [1, 2, 3.33]
L = [1, 2, 3]
Apply factorial to each element of [1, 2, 3]
L = [1, 2, 6]
Apply Fibonnaci to each element of [1, 2, 6]
L = [1, 2, 13]
파이썬에서 함수는 일급 객체이다. 즉 함수는 int나 list와 같은 모든 자료형들과 같이 객체로 취급할 수 있다는 뜻이다. 함수에도 자료형이 있기 때문에 예를 들어 type (fact)라는 수식을 입력하면 <type 'function'>이라는 결과가 나온다.
# 함수를 리스트의 요소로 사용하기
def applyToEach(L, f):
for i in range(len(L)):
L[i] = f(L[i])
def factR(n):
if n == 1:
return n
else:
return n*factR(n-1)
def fib(n):
if n == 0 or n ==1:
return 1
else:
return fib(n-1) + fib(n-2)
L = [1, -2, 3.33]
print('L =', L)
print('Apply abs to each element of L.')
applyToEach(L, abs) # 절대 값
print('L =', L)
print('Apply int ot each element of', L)
applyToEach(L, int)
print('L =', L)
print('Apply factorial to each element of', L)
applyToEach(L, factR)
print('L =', L)
print('Apply Fibonnaci to each element of', L)
applyToEach(L, fib)
print('L =', L)
함수 applyToEach는 함수로 된 인자를 가지고 있기 때문에 고차함수라 불린다 . 처음 호출되었을 때 applyToEach는 단항 내장 함수인 abs를 각 요소에 적용하여 리스트 L을 변형시킨다. 두 번째 호출되었을 때는 각 요소를 형식 변환한다. 세번째 호출되었을 때는 각 요소들에 정의된 factR 함수를 적용한다. 네 번째 호출되었을 때는 각 요소들에게 정의된 fib 함수를 적용한다.
출력 결과
L = [1, -2, 3.33]
Apply abs to each element of L.
L = [1, 2, 3.33]
Apply int ot each element of [1, 2, 3.33]
L = [1, 2, 3]
Apply factorial to each element of [1, 2, 3]
L = [1, 2, 6]
Apply Fibonnaci to each element of [1, 2, 6]
L = [1, 2, 13]
파이썬은 for문에서 반복문이 한 번 돌고 날 때마다 증가되는 내부 카운터를 사용하여 리스트에서 실행되는 지점을 따라가도록 설계되었다. 만약 카운터의 값이 증가하다가 리스트 길이와 같은 값에 도달하면 반복문이 종료된다.
# 복제하기(Cloning)
def removeDups(L1, L2):
for e1 in L1:
if e1 in L1:
L1.remove(e1)
L1 = [1,2,3,4]
L2 = [1,2,4,6]
removeDups(L1, L2)
print('L1 =', L1)
위 예제의 경우 숨겨진 카운터가 0에서 시작하여 L1[0]이 L2에 있다는 것을 발견하고는 리스트에서 제거한다. 그래서 L1의 길이가 3이 된다. 그리고서 카운터는 1 증가하고 L1[1]이 L2에 있는지 확인하게 된다. 하지만 이것은 L1[1]이 원래 가지고 있었던 값(즉 2)가 아니라 2가 제거된 후의 L1[1]의 값, 즉 3이다. 그러므로 보시다시피 리스트가 반복문 안에서 수정된다면 원하는 결과를 얻지 못하게 되는것이다.
파이썬은 for문에서 반복문이 한 번 돌고 날 때마다 증가되는 내부 카운터를 사용하여 리스트에서 실행되는 지점을 따라가도록 설계되었다. 만약 카운터의 값이 증가하다가 리스트 길이와 같은 값에 도달하면 반복문이 종료된다.
# 복제하기(Cloning)
def removeDups(L1, L2):
for e1 in L1:
if e1 in L1:
L1.remove(e1)
L1 = [1,2,3,4]
L2 = [1,2,4,6]
removeDups(L1, L2)
print('L1 =', L1)
위 예제의 경우 숨겨진 카운터가 0에서 시작하여 L1[0]이 L2에 있다는 것을 발견하고는 리스트에서 제거한다. 그래서 L1의 길이가 3이 된다. 그리고서 카운터는 1 증가하고 L1[1]이 L2에 있는지 확인하게 된다. 하지만 이것은 L1[1]이 원래 가지고 있었던 값(즉 2)가 아니라 2가 제거된 후의 L1[1]의 값, 즉 3이다. 그러므로 보시다시피 리스트가 반복문 안에서 수정된다면 원하는 결과를 얻지 못하게 되는것이다.
# for문을 사용한 리스트와 가변성
Techs = ['MIT', 'Caltech']
Ivys = ['Harvard', 'Yale', 'Brown']
Univs = [Techs, Ivys]
Univs1 = [['MIT', 'Caltech'], ['Harvard', 'Yale', 'Brown']]
Techs.append('RPI')
for e in Univs:
print('Univs contains', e)
print('which contains')
for u in e:
print(' ', u)
출력 결과
Univs contains ['MIT', 'Caltech', 'RPI']
which contains
MIT
Caltech
RPI
Univs contains ['Harvard', 'Yale', 'Brown']
which contains
Harvard
Yale
Brown
Techs.append(RPI) 명령어처럼 리스트에 또 다른 리스트를 끝에 추가할 떄 추가하는 리스트의 구조는 유지된다. 즉 리스트 형태로 리스트 끝에 삽입되는 것이다. 만약 원래의 리스트 구조를 유지하지 않고 리스트에 있는 요소를 다른 리스트에 삽입하려고 한다면 리스트 합치기나 extend 메소드를 사용하면 된다.
리스트에 삽입할 때 연산자 +를 사용하거나 extend를 사용한 경우 삽입하려는 값이 리스트 형태로 삽입되지 않고 요소들만 삽입된 것을 볼 수 있다. 출력된 결과를 보면 연산자 +는 새로운 리스트를 생성하고 반환한다. 그와 반대로 extend와 append는 L1을 변형시킨다.
※ 리스트에 관한 몇 개의 주요 메소드들
L.append(e) : L의 끝에 객체 e를 추가한다.
L.count(e) : L안에 있는 e의 개수를 반환한다.
L.insert(i, e) : L의 i번째 인덱스에 객체 e를 삽입한다.
L.extend(L1) : L1의 요소들을 L의 끝에 추가한다.
L.remove(e) : L에서 나타나는 첫 번째 e를 삭제한다.
L.index(e) : L에서 나타나는 첫 번째 e의 인덱스를 반환한다. 만약 e가 L에 없는 경우 예외가 발행한다.
L.pop(i) : L의 i번째 인덱스에 있는 값을 반환과 동시에 리스트에서 제거한다. i의 값이 생략된 경우 i는 디폴드 값인 -1가 되며 L의 마지막 요소를 반환하고 리스트에서 제거한다.
튜플과 마찬가지로 리스트 또한 순서가 있는 일련의 값들인데, 인덱스를 사용하여 각 값을 구분한다. list 자료형을 표현하는 문법은 튜플과 매우 비슷하지만 차이점은 소괄호() 대신 대괄호[]를 사용한다는 것이다. 리스트와 튜플에는 매우 중요한 차이점이 하나 있다. 리스트는 변형이 가능한 반면에 튜플과 문자열은 변형이 불가능하다. 변형 불가능한 객체들을 생성하는데 사용할 수 있는 많은 연산자들이 있으며 또한 이러한 자료형을 가진 객체도 변수들과 바인딩한다. 반면에 list 자료형의 객체들은 생성된 이후에 수정이 가능하다.
객체를 변형시키는 것과 변수에 객체를 변수에 대입하는 것의 차이가 처음에는 미묘하게 보일 수 있다. 하지만 파이썬에서 변수는 객체에 붙이는 라벨처럼 이름에 불과하다는 것을 되새긴다면 좀 더 명확하게 이해하는데 도움이 될 것이다.
다음 두 줄의 서술문이 실행된다고 가정하면 인터프리터는 두 개의 새로운 리스트를 생성하고 Techs는 ['MIT', 'Caltech'], Ivys는 ['Harvard', 'Yale', 'Brown']에 바인딩한다.
출력 결과를 보면 보기에는 Univs와 Univs1이 마치 같은 값에 바인딩된 것처럼 보이지만 매우 다른 값에 바인딩되어 있다.
Univs와 Univs1가 다른 값에 바인딩되어 있는 것은 파이썬 내장 함수 id를 사용하여 증명할 수 있다. id는 각 객체의 고유한 정수 식별자를 반환하여 두 객체가 동일한지 검사할 때 사용할 수 있다.
print(Univs == Univs1) # test value equality
print(id(Univs) == id(Univs1)) # test object equality
print('Id of Univs =', id(Univs))
print('Id of Univs1 =', id(Univs1))
출력 결과는 다음과 같다.
True
False
Id of Univs = 4364976768
Id of Univs1 = 4364976960
위의 그림을 보면 Univs의 요소들은 Techs와 Ivys가 바인딩된 리스트가 복제된것이이 아니며 또 하나의 리스트이다. Univs1의 요소들은 Univs와 같은 요소들을 담고 있는 리스트이지만 Univs와는 다른 개별적인 리스트이다. 다음의 코크를 실행해보면 더 잘 알 수 있다.
print('Ids of Univs[0] and Univs[1]', id(Univs[0]), id(Univs[1]))
print('Ids of Univs1[0] and Univs1[1]', id(Univs1[0]), id(Univs1[1]))
출력 결과는 다음과 같다.
Id of Univs1 = 4364976960
Ids of Univs[0] and Univs[1] 4364784896 4364884672
Ids of Univs1[0] and Univs1[1] 4364785024 4364976832
이것이 뭐가 그렇게 중요할지는 모르지만 리스트가 변형 가능하기 때문에 중요하다.
Techs.append('RPI')
append 메소드는 새로운 리스트를 생성하지 않고, 이미 존재하고 있는 리스트 Techs에 문자열 'RPI'로 된 새로운 요소를 맨 끝에 집어 넣어서 리스트를 변형시킨다.
Univs는 아직도 같은 두 개의 리스트를 가지고 있지만 그 중 하나의 리스트는 값이 바뀌었다.
이것을 엘리어싱이라고 부른다. 한 개의 같은 리스트 객체에 두 개의 개별적인 경로가 있는 것이다. 첫 번째 경로는 변수 Techs를 통하여 또 다른 경로는 Univs에 바인딩된 list 객체의 첫 번째 요소를 통한다. 두 경로 중 어느것이든 사용하여 객체를 변형시킬 수가 있으며, 두 경로 모두를 통해서 변형된 효과를 볼 수 있다.
시퀀스(튜플이나 문자열)의 길이를 먼저 안다면 파이썬의 다중 대입문을 사용하여 각 요소를 뽑아내는 것이 편할 것이다.
예를 들어 서술믄 x, y = (3, 4)를 실행하면 x는 3에 바인딩되고 y는 4에 바인딩 된다. 마찬가지로 a, b, c = 'xyz'를 실행하면 a는 'x'에, b는 'y'에, c는 'z'에 바인딩 된다. 이 기능은 고정된 길이의 시퀀스를 반환하는 함수들과 함께 사용하면 특히 더 편리할 때가 있다.
다음 함수를 살펴보면, 다중 대입문에서 minDivisor는 2, maxDivisor는 100이 바인딩될 것이다.
# 시퀀스와 다중 대입문
def findExtremeDivisors(n1, n2):
divisors = () # the empty tuple
minVal, maxVal = None, None
for i in range(2, min(n1, n2) + 1):
if n1%i == 0 and n2%i == 0:
if minVal == None or i < minVal:
minVal = i
if maxVal == None or i > maxVal:
maxVal = i
return (minVal, maxVal)
minDivisor, maxDivisor = findExtremeDivisors(100, 200)
print('최소 약수 : ', minDivisor)
print('최대 약수 : ', maxDivisor)
튜플은 문자열과 같이 일련의 요소들이 순서대로 나열된 것이다. 튜플과 문자열의 차이점은 튜플의 요소들은 문자가 아니어도 된다는 것이다. 어떤 자료형도 튜플의 요소가 될 수 있으며 일련의 요소들이 다 같은 자료형이 아니어도 된다.
튜플의 상수는 갈호 안에 쉼표로 나누어진 리스트를 써서 표현한다. 튜플은 문자열과 같이 합치기, 인덱스, 슬라이스를 적용할 수 있다.
for문을 사용하면 튜플의 요소들을 돌아가면서 한 개씩 접근할 수 있다.
# 20과 100의 공통분모를 출력한 후에 구한 모든 공통분모의 합을 출력
def findDivisors (n1, n2):
divisors = () #the empty tuple
for i in range(1, min(n1, n2) + 1):
if n1%i == 0 and n2%i == 0:
divisors = divisors + (i,)
return divisors
divisors = findDivisors(20, 100)
print('20과 100의 공통분모 : ', divisors)
total = 0
for d in divisors:
total += d
print('공통분모의 합 : ', total)
모든 컴퓨터 시스템은 계산된 것을 저장하기 위해서 파일들을 사용한다. 파이썬은 파일을 생성하고 접근할 때 사용하는 기능들이 많이 제공한다. 모든 운영체제(윈도우나 맥)는 파일을 생성하고 접근하는 자기만의 파일 시스템을 갖고 있다. 파이썬은 파일핸들을 이용해서 파일을 접근한다 그렇기 때문에 파이썬은 운영체제에 종속되지 않고 독립적이다.
nameHandle = open('kids', 'w')
위 코드는 운영체제에게 kids라는 이름을 가진 파일을 생성하고 그 파일의 파일핸들을 반환하라고 지시한다.
open 함수의 인수 'w'는 파일이 쓰기용임을 명시하는 것이다.
# 파일 쓰기
nameHandle = open('kids', 'w')
for i in range(2):
name = input('Enter name: ')
nameHandle.write(name + '\n')
nameHandle.close()
위의 코드는 파일을 먼저 열고 쓰기 방법을 사용하여 두 줄을 쓴 후에 파일을 닫는다.
글자 입력
Enter name: David
Enter name: Andrea
위와 같이 문자를 입력하면 생성된 kids파일안에 문자가 입력된 것을 볼 수 있다.
이제는 읽기용으로 인수 'r'을 사용해서 파일을 열고 그 내용을 출력해본다. 파이썬은 파일을 일련의 줄로 취급하기 때문에 for문을 사용하여 파일의 내용을 한 줄 씩 읽을 수 있다.
#파일 읽기
nameHandle = open('kids', 'r')
for line in nameHandle:
print(line[:-1])
nameHandle.close()
파일에 David과 Andrea라는 이름이 입력되어 있다면 다음과 같이 출력된다.
출력 결과
David
Andrea
다음은 kids파일에 인수 'a'를 사용하여 문자 추가를 해볼 것이다.
#파일에 문자 추가하기
nameHandle = open('kids', 'a')
nameHandle.write('Michael\n')
nameHandle.write('Mark\n')
nameHandle.close()
nameHandle = open('kids', 'r')
for line in nameHandle:
print(line[:-1])
nameHandle.close()
그러면 기존 이름 'David, Adrea'외 'Michae, Mark'가 추가 된 것을 볼 수 있다.
추가 된 이름
※ 다음은 파일을 다루는 기본적인 함수들이다.
open(fn, 'w') : 쓰기용으로 파일을 생성하고 파일핸들을 반환한다.
open(fn, 'r') : 읽기용으로 기존의 파일을 열고 파일핸들을 반환한다.
open(fn, 'a') : 이어쓰기로 기존의 파일을 열고 파일핸들을 반환한다.
fh.read() : 파일의 내용을 담고 있는 문자열과 이에 관련된 파일핸들 fh를 반환한다.
fh.readline() : 다음 줄과 이에 관련된 파일핸들 fh를 반환한다.
fh.readlines() : 각 줄의 목록과 이에 관련된 파일핸들 fh를 반환한다.
fh.write(s) 파일핸들 fh에 관련된 파일의 끝에 문자열 s를 쓴다.
fh.writeLines(S) : S는 일련의 문자열, 파일핸들 fh에 관련된 파일의 끝에 S에 있는 내용을 쓴다.
원의 넓이 : 28.27431
원의 둘레 : 18.849539999999998
구의 겉넓이 : 113.09724
구의 부피 : 113.09723999999999
다음 다른 프로그램에서 import문을 통해서 모듈에 접근할 수 있다.
프로그램 파일을 하나 만들고 import 문을 사용하여 위의 함수들을 호출하는 코딩을 만든다.
import circle
print(circle.pi)
print(circle.area(3))
print(circle.circumference(3))
print(circle.sphereSurface(3))
print(circle.sphereVolueme(3))
그러면 다음과 같이 출력 결과가 나온다.
원의 넓이 : 28.27431
원의 둘레 : 18.849539999999998
구의 겉넓이 : 113.09724
구의 부피 : 113.09723999999999
3.14159
28.27431
18.849539999999998
113.09724
113.09723999999999
위의 출력 결과에서 본것처럼 import circle을 호출할 경우 circle.py에 있는 출력결과가 나오고, 또한 파일안에 정의한 개별 함수도 호출된것을 볼 수 있다. import M을 수행하면 모듈 M을 자기 영역에 바인딩하여 그 내용을 모두 가져온다. 그러므로 import를 할 때에는 점 표기법을 사용하여 어떤 모듈에 정의된 이름을 사용할 것인지 명시해주어야 한다. 예를 들어 circle.py 파일 밖에서 pi와 circle.pi는 두 개의 다른 객체일 수 있는 것이다.
프로그램을 import 할 때 모듈 이름을 생략하고 모듈 안에 정의된 이름을 접근할 수 있는 방법이 있다. 서술문 from M import*을 사용하면 사용 중인 영역에서 M에 정의된 모든 객체들을 바인딩하게 된다.
from circle import *
print(pi)
print(area(3))
출력결과
원의 넓이 : 28.27431
원의 둘레 : 18.849539999999998
구의 겉넓이 : 113.09724
구의 부피 : 113.09723999999999
3.14159
28.27431
각 함수 안에 있는 global numFibCalls는 파이썬에게 numFibCalls가 모듈의 가장 바깥 영역에 정의되어야 함을 알려준다. 만약 global numFibCalls라는 코드가 없다면, 두 함수 모두에서 numFibCalls는 지역변수가 되었을 것이다. fib와 testFib 함수에서 numFibCalls가 참조하는 어떤 객체든지 아무런 제한없이 접근할 수 있다. testFib는 호출될 때마다 numFibCalls를 0에 바인딩하며, fib은 실행될 때마다 numFibCalls의 값을 1씩 증가시킨다.
# 전역 변수의 사용
def fib(x):
global numFibCalls
numFibCalls += 1
if x == 0 or x == 1:
return 1
else:
return fib(x-1) + fib(x-2)
def testFib(n):
for i in range(n+1):
global numFibCalls
numFibCalls = 0
print('fib of', i, '=', fib(i))
print('fib called', numFibCalls, 'times.')
print('testFib :', testFib(5))
출력결과
fib of 0 = 1
fib called 1 times.
fib of 1 = 1
fib called 1 times.
fib of 2 = 2
fib called 3 times.
fib of 3 = 3
fib called 5 times.
fib of 4 = 5
fib called 9 times.
fib of 5 = 8
fib called 15 times.
testFib : None
각 함수 안에 있는 global numFibCalls는 파이썬에게 numFibCalls가 모듈의 가장 바깥 영역에 정의되어야 함을 알려준다.
만약 global numFibCalls라는 코드가 없다면, 두 함수 모두에서 numFibCalls는 지역변수가 되었을 것이다.
fib와 testFib 함수에서 numFibCalls가 참조하는 어떤 객체든지 아무런 제한없이 접근할 수 있다.
testFib는 호출될 때마다 numFibCalls를 0에 바인딩하며, fib은 실행될 때마다 numFibCalls의 값을 1씩 증가시킨다.
# 전역 변수의 사용
def fib(x):
global numFibCalls
numFibCalls += 1
if x == 0 or x == 1:
return 1
else:
return fib(x-1) + fib(x-2)
def testFib(n):
for i in range(n+1):
global numFibCalls
numFibCalls = 0
print('fib of', i, '=', fib(i))
print('fib called', numFibCalls, 'times.')
print('testFib :', testFib(5))
출력결과
fib of 0 = 1
fib called 1 times.
fib of 1 = 1
fib called 1 times.
fib of 2 = 2
fib called 3 times.
fib of 3 = 3
fib called 5 times.
fib of 4 = 5
fib called 9 times.
fib of 5 = 8
fib called 15 times.
testFib : None
예를 들어 새로 태어난 두 마리 토끼가 우리 안에 있다고 가정해본다. 한마리는 수컷이고 한 마리는 암컷이다. 이 토끼들은 출생한지 한 달이 지나면 가능하며 임신 기간은 한 달이라고 해본다.
만약 이런 특성을 가진 토끼들이 절대로 죽지 않으며 암토끼가 출산할 때마다 수컷과 암컷 토끼를 한마리씩 낳는다고 하면 6개월 후 임신한 토끼는 몇마리나 될까?
개월
암토끼
0
1
1
1
2
2
3
3
4
5
5
8
6개월 후 임신한 토끼 마리 수
6
13
위의 테이블에서 개월 수가 1보다 큰 경우의 식은 다음과 같다.
females(n) = females(n-1) + females(n-2)
각 암토끼는 n-1개월 동안 살아있으며 n개월에도 살아있을 것이다. 거기에다 각 n-2개월에 살아있던 각 암토끼는 n개월에 새로운 암토끼를 출생할 것이다. 그러므로 새로 출생한 암토끼의 수를 n-1개월에 살아있는 암토끼의 수에 더하여 n개월에 살아있는 암토끼의 수를 구하게 된다.
# 피나보치 수열의 재귀적 구현
def fib(n):
"""Assume n an int >= 0
Returns Fibonacci of n"""
if n == 0 or n ==1:
return 1
else:
return fib(n-1) + fib(n-2)
def testFib(n):
for i in range(n+1):
print('fib of', i, '=', fib(i))
print('n개월 후 수량 : ', fib(6))
print('testFib : ', testFib(6))
출력결과
n개월 후 토끼 마리 수 : 13
fib of 0 = 1
fib of 1 = 1
fib of 2 = 2
fib of 3 = 3
fib of 4 = 5
fib of 5 = 8
fib of 6 = 13
testFib : None
예를 들어 새로 태어난 두 마리 토끼가 우리 안에 있다고 가정해본다. 한마리는 수컷이고 한 마리는 암컷이다. 이 토끼들은 출생한지 한 달이 지나면 가능하며 임신 기간은 한 달이라고 해본다.
만약 이런 특성을 가진 토끼들이 절대로 죽지 않으며 암토끼가 출산할 때마다 수컷과 암컷 토끼를 한마리씩 낳는다고 하면 6개월 후 임신한 토끼는 몇마리나 될까?
개월
암토끼
0
1
1
1
2
2
3
3
4
5
5
8
6개월 후 임신한 토끼 마리 수
6
13
위의 테이블에서 개월 수가 1보다 큰 경우의 식은 다음과 같다.
females(n) = females(n-1) + females(n-2)
각 암토끼는 n-1개월 동안 살아있으며 n개월에도 살아있을 것이다. 거기에다 각 n-2개월에 살아있던 각 암토끼는 n개월에 새로운 암토끼를 출생할 것이다. 그러므로 새로 출생한 암토끼의 수를 n-1개월에 살아있는 암토끼의 수에 더하여 n개월에 살아있는 암토끼의 수를 구하게 된다.
# 피나보치 수열의 재귀적 구현
def fib(n):
"""Assume n an int >= 0
Returns Fibonacci of n"""
if n == 0 or n ==1:
return 1
else:
return fib(n-1) + fib(n-2)
def testFib(n):
for i in range(n+1):
print('fib of', i, '=', fib(i))
print('n개월 후 수량 : ', fib(6))
print('testFib : ', testFib(6))
출력결과
n개월 후 토끼 마리 수 : 13
fib of 0 = 1
fib of 1 = 1
fib of 2 = 2
fib of 3 = 3
fib of 4 = 5
fib of 5 = 8
fib of 6 = 13
testFib : None
첫 번째 factI(n)식은 바탕 명제를 정의한것이고, 두 번째 factR식은 이전에 계승된 수에 관해서 바탕 명제를 제외한 모든 자연수의 계승을 정의한것이다.
#계승을 반복함수로 구현한 factI와 재귀함수로 구현한 factR 코드
def factI(n):
"""Assumes that n is an int > 0
Returns n!"""
result = 1
while n > 1:
result = result*n
n -= 1
return result
def factR(n):
"""Assumes that n is an int > 0
Returns n!"""
if n == 1:
return n
else:
return n*factR(n-1)
print('factI : ', factI(1))
print('factR : ',factR(1))
출력결과
먼저 factI 함수부터 살펴보면 n=1인 경우 반복문 while n > 1이 리턴하지 않으므로 결과는 None이 된다.
그리고 factR 함수는 n = 1인 경우 결과값 1을 그대로 반환하게 된다.
factI : None
factR : 1
factI에서 n = 2인 경우 result = result*n → result = 1*2가 되어 결과 값은 2가 된다.
factR에서 n = 2인 경우 n*(n-1)! = 2x(2-1)=2가 출력된다.
factI : 2
factR : 2
factI에서 n = 3인 경우 result = result*n → result = 1*3가 되어 결과 값은 3가 된다.
factR에서 n = 3인 경우 n*(n-1)! = 3x(3-1)!=3x2!이 되어 6이 출력된다.
findRoot 함수는 제곱근을 찾는데 사용했던 이분 검색을 일반화하는 함수이고, testFindRoot는 findRoot가 제대로 작동하는지 검사할 때 사용하는 함수이다.
프로그램이 제대로 작동하지 않을 때 문제가 어디서 발생하는지 찾고 그것을 고치는 작업을 할 때 만약 미리 테스트 코드를 짜놓는다면 매번 쉘에 시험사례를 입력하여 검사를 하는것 보다 훨씬 수월하게 오류를 찾아낼 수 있다.
#근의 근사값 찾기
def findRoot(x, power, epsilon):
"""Assumes x and epsilon int or float, power an int,
epsilon > 0,& power >=1
Returns float y such that y**power is within epsilon of x.
If such a float does not exist, it returns None"""
if x < 0 and power%2 == 0:
return None
low = min(-1.0, x)
high = max(1.0, x)
ans = (high + low)/2.0
while abs(ans**power - x) >= epsilon:
if ans**power < x:
low = ans
else:
high = ans
ans = (high + low)/2.0
return ans
def testFindRoot():
epsilon = 0.0001
for x in (0.25, -0.25, 2, -2, 8, -8):
for power in range(1, 4):
print('Testing x = ' + str(x) + 'and power = ' + str(power))
result = findRoot(x, power, epsilon)
if result == None:
print('No root')
else:
print(' ', result**power, '~=', x)
print('testFindRoot :', testFindRoot())
print('findRoot : ', findRoot(4, 2, 0.1))
findRoot의 사양은 그 사양을 만족시키는 모든 구현 가능한 것들을 추상화한 것이다.
findRoot의 고객은 구현된것이 사양대로 작동한다고 가정할 수 있지만 고객은 그 이상 어떤 것도 추정해서는 안된다.
예를 들어 고객이 findRoot(4.0, 2, 0.1)을 호출하면 제곱으로 3.99에서 4.01 사이의 값을 가진 어떤 값을 변환한다는 것을 추정할 수 있다. 그 값은 양수일 수도 있고 음수일수 있으며 4.0이 완전 제곱이라 할지라고 반환된 완전 제곱 값은 2.0이나 -2.0이 아닐 수도 있다.
출력결과
Testing x = 0.25and power = 1
0.25 ~= 0.25
Testing x = 0.25and power = 2
0.25 ~= 0.25
Testing x = 0.25and power = 3
0.24990749079734087 ~= 0.25
Testing x = -0.25and power = 1
-0.25 ~= -0.25
Testing x = -0.25and power = 2
No root
Testing x = -0.25and power = 3
-0.24990749079734087 ~= -0.25
Testing x = 2and power = 1
1.999908447265625 ~= 2
Testing x = 2and power = 2
2.0000906325876713 ~= 2
Testing x = 2and power = 3
2.000059155646067 ~= 2
Testing x = -2and power = 1
-1.999908447265625 ~= -2
Testing x = -2and power = 2
No root
Testing x = -2and power = 3
-2.000059155646067 ~= -2
Testing x = 8and power = 1
7.999931335449219 ~= 8
Testing x = 8and power = 2
7.99999568007479 ~= 8
Testing x = 8and power = 3
8.000068664747232 ~= 8
Testing x = -8and power = 1
-7.999931335449219 ~= -8
Testing x = -8and power = 2
No root
Testing x = -8and power = 3
-8.000068664747232 ~= -8
testFindRoot : None
findRoot : 2.0078125