본문 바로가기

함수의 합성과 종류

두 함수를 이어 적용하는 합성의 순서와 정의역 조건, 결합법칙, 합성의 역함수를 익히고, 항등함수·상수함수·바닥과 천장 함수·수열을 다룹니다. 더 깊이에서는 집합의 포함 여부를 0과 1로 나타내는 특성함수를 봅니다.

온도를 섭씨에서 화씨로 바꾼 뒤 소수점 아래를 반올림하는 것과, 먼저 반올림한 뒤 화씨로 바꾸는 것은 결과가 다를 수 있습니다. 두 계산을 이어서 할 때는 어느 것을 먼저 하는지가 중요합니다. 이 글에서는 함수 두 개를 이어 적용하는 합성을 정의하고, 그 순서와 조건을 확인한 뒤, 프로그래밍에서 자주 만나는 함수들을 살펴봅니다.

합성은 오른쪽 함수부터

f:A→Bf:A\to B와 g:B→Cg:B\to C가 있으면, x에 f를 먼저 적용하고 그 결과에 g를 적용하는 함수를 만들 수 있습니다. 이것을 f와 g의 합성(composition)이라 하고 g∘fg\circ f로 씁니다. (g∘f)(x)=g(f(x))(g\circ f)(x)=g(f(x))이므로, 기호에서는 오른쪽에 적힌 f를 먼저 적용합니다. 안쪽 괄호부터 계산한다고 생각하면 기억하기 쉽습니다.

실수에서 f(x)=x+2f(x)=x+2, g(x)=3xg(x)=3x라면 (g∘f)(x)=g(x+2)=3x+6(g\circ f)(x)=g(x+2)=3x+6이고, (f∘g)(x)=f(3x)=3x+2(f\circ g)(x)=f(3x)=3x+2입니다. 두 합성은 다른 함수이므로, 합성은 일반적으로 순서를 바꿀 수 없습니다. x = 0만 넣어 봐도 6과 2로 다릅니다.

f = lambda x: x + 2
g = lambda x: 3 * x
print(g(f(0)), f(g(0)))
print(g(f(5)), f(g(5)))
6 2
21 17

합성을 하려면 앞 함수의 출력이 뒤 함수의 입력이 될 수 있어야 합니다. 곧 f의 치역이 g의 정의역 안에 들어 있어야 합니다. f가 -1을 출력할 수 있는데 g가 0 이상의 수에서만 정의된 제곱근 함수라면, 그런 x에 대해서는 g(f(x))g(f(x))를 계산할 수 없습니다. 프로그램에서 앞 함수의 반환값이 뒤 함수가 받는 형식과 맞아야 하는 것과 같습니다.

합성에는 결합법칙 (h∘g)∘f=h∘(g∘f)(h\circ g)\circ f=h\circ(g\circ f)가 성립합니다. 양쪽 모두 f, g, h를 이 순서로 적용하는 것이라 결과가 같습니다. 괄호를 어떻게 묶든 적용하는 순서는 바뀌지 않는다는 뜻이지, 함수의 순서를 바꾸어도 된다는 뜻이 아닙니다.

f와 g가 모두 전단사이면 합성도 전단사이고, 그 역함수는 (g∘f)−1=f−1∘g−1(g\circ f)^{-1}=f^{-1}\circ g^{-1}입니다. f를 먼저, g를 나중에 적용했으므로, 되돌릴 때는 나중에 적용한 g를 먼저 되돌리고 그다음 f를 되돌려야 하기 때문입니다. 양말을 신고 신발을 신었다면, 벗을 때는 신발부터 벗는 것과 같습니다. 앞의 예에서 g∘fg\circ f는 y=3x+6y=3x+6입니다. 이것을 되돌리려면 먼저 g−1(y)=y/3g^{-1}(y)=y/3으로 3을 나누고, 그다음 f−1f^{-1}로 2를 빼야 하므로 f−1(g−1(y))=y/3−2=(y−6)/3f^{-1}(g^{-1}(y))=y/3-2=(y-6)/3입니다. 실제로 y=3x+6y=3x+6을 x에 대해 풀어도 같은 식이 나옵니다.

자주 만나는 함수들

항등함수(identity function) id(x)=x\mathrm{id}(x)=x는 입력을 그대로 돌려줍니다. 어떤 함수와 합성해도 그 함수가 그대로이므로, 수의 곱셈에서 1과 같은 역할을 합니다. 함수 f가 역함수를 가지면 f−1∘ff^{-1}\circ f와 f∘f−1f\circ f^{-1}은 각각의 정의역에서 항등함수입니다. 상수함수 f(x)=cf(x)=c는 모든 입력을 같은 값으로 보내므로, 정의역에 원소가 둘 이상이면 단사가 아닙니다.

바닥 함수(floor function) ⌊x⌋\lfloor x\rfloor는 x 이하의 정수 가운데 가장 큰 것이고, 천장 함수(ceiling function) ⌈x⌉\lceil x\rceil는 x 이상의 정수 가운데 가장 작은 것입니다. ⌊3.7⌋=3\lfloor3.7\rfloor=3, ⌈3.2⌉=4\lceil3.2\rceil=4입니다. 음수에서는 주의해야 합니다. ⌊−1.2⌋\lfloor-1.2\rfloor는 -1.2 이하의 가장 큰 정수인 -2이고, ⌈−1.2⌉\lceil-1.2\rceil는 -1입니다. 소수점 아래를 잘라 버리는 것은 0 쪽으로 가므로, 음수에서는 바닥 함수와 결과가 다릅니다.

import math

print(math.floor(-1.2), math.ceil(-1.2), int(-1.2))
print(math.ceil(23 / 5), 23 // 5, -(-23 // 5))
-2 -1 -1
5 4 5

Python의 int는 소수점 아래를 잘라 -1.2를 -1로 만들지만, math.floor는 -2를 돌려줍니다. 물건 23개를 상자에 5개씩 담으면, 꽉 찬 상자는 ⌊23/5⌋=4\lfloor23/5\rfloor=4개이고 남은 3개까지 담으려면 상자가 ⌈23/5⌉=5\lceil23/5\rceil=5개 필요합니다. 정수만으로 천장을 구할 때는 -(-23 // 5)처럼 부호를 두 번 뒤집어 바닥 나눗셈을 쓰면, 실수 나눗셈의 반올림 오차를 피할 수 있습니다.

수열은 자연수의 함수다

수열 a0,a1,a2,…a_0,a_1,a_2,\ldots는 자연수 n을 넣으면 n번째 항 ana_n이 나오는 함수로 볼 수 있습니다. an=2n+1a_n=2n+1은 n = 0부터 1, 3, 5, 7, …을 만듭니다. 같은 수열을 바로 앞의 항으로 정할 수도 있습니다. a0=1a_0=1이고 an+1=an+2a_{n+1}=a_n+2라고 하면, 첫 항에서 시작해 2씩 더해 같은 수열이 나옵니다. 이렇게 앞의 항으로 다음 항을 정하는 것을 재귀적 정의 또는 점화식이라고 합니다.

closed = [2 * n + 1 for n in range(5)]
recursive = [1]
for n in range(4):
    recursive.append(recursive[-1] + 2)
print(closed, recursive)
[1, 3, 5, 7, 9] [1, 3, 5, 7, 9]

점화식만 있고 시작값이 없으면 수열이 하나로 정해지지 않습니다. an+1=an+2a_{n+1}=a_n+2만으로는 a0=1a_0=1이면 홀수의 수열이, a0=0a_0=0이면 짝수의 수열이 되기 때문입니다. 두 정의가 같은 수열을 만든다는 것은 수학적 귀납법으로 증명할 수 있습니다.

더 깊이: 포함 여부를 나타내는 특성함수

전체집합 U의 부분집합 A에 대해, 원소 x가 A에 속하면 1, 속하지 않으면 0을 돌려주는 함수 χA:U→{0,1}\chi_A:U\to\{0,1\}을 A의 특성함수(characteristic function)라고 합니다. A 밖의 원소에도 출력 0이 있으므로, U의 모든 원소에서 정의된 함수입니다. U={1,2,3,4}U=\{1,2,3,4\}, A={2,4}A=\{2,4\}이면 입력 1, 2, 3, 4의 출력은 차례로 0, 1, 0, 1이고, 합 기호로 ∑x∈UχA(x)=0+1+0+1=2=∣A∣\sum_{x\in U}\chi_A(x)=0+1+0+1=2=|A|처럼 원소의 개수를 셀 수 있습니다.

특성함수로 집합의 연산을 계산할 수도 있습니다. 교집합은 χA∩B(x)=χA(x)⋅χB(x)\chi_{A\cap B}(x)=\chi_A(x)\cdot\chi_B(x)입니다. 두 값이 모두 1일 때만 곱이 1이기 때문입니다. 합집합은 두 값을 그냥 더하면 두 집합에 모두 속한 원소에서 2가 나와 0과 1이 아니게 되므로, 겹친 만큼을 빼서 χA∪B=χA+χB−χAχB\chi_{A\cup B}=\chi_A+\chi_B-\chi_A\chi_B로 계산합니다. 포함·배제의 원리를 원소 하나에서 계산한 것과 같습니다.

U = [1, 2, 3, 4]
A, B = {2, 4}, {1, 2}


def chi(S):
    return lambda x: 1 if x in S else 0


cA, cB = chi(A), chi(B)
print([cA(x) for x in U], sum(cA(x) for x in U))
print([cA(x) * cB(x) for x in U], [chi(A & B)(x) for x in U])
print([cA(x) + cB(x) - cA(x) * cB(x) for x in U], [chi(A | B)(x) for x in U])
[0, 1, 0, 1] 2
[0, 1, 0, 0] [0, 1, 0, 0]
[1, 1, 0, 1] [1, 1, 0, 1]

chi(S)는 집합 S를 받아 그 특성함수를 돌려주는 함수입니다. 곱과 보정한 합이 교집합과 합집합의 특성함수와 모든 원소에서 같습니다. 사건이 일어났는지를 0과 1로 나타내는 지시변수도 같은 생각입니다.

정리

  • 합성 g∘fg\circ f는 f를 먼저, g를 나중에 적용하며 (g∘f)(x)=g(f(x))(g\circ f)(x)=g(f(x))입니다. 일반적으로 g∘fg\circ f와 f∘gf\circ g는 다릅니다.
  • 합성하려면 앞 함수의 치역이 뒤 함수의 정의역 안에 있어야 하고, 결합법칙은 괄호만 바꿀 뿐 적용 순서를 바꾸지 않습니다.
  • 전단사인 두 함수의 합성의 역함수는 (g∘f)−1=f−1∘g−1(g\circ f)^{-1}=f^{-1}\circ g^{-1}로, 나중에 적용한 것부터 되돌립니다.
  • 바닥 함수는 x 이하의 가장 큰 정수, 천장 함수는 x 이상의 가장 작은 정수이며, 음수에서는 소수점 아래를 잘라 내는 것과 다릅니다.
  • 수열은 자연수의 함수이고, 점화식으로 정할 때는 시작값이 있어야 하나로 정해집니다.

연습 문제

1. 합성의 순서

f(x)=x2f(x)=x^2, g(x)=x+1g(x)=x+1일 때 (g∘f)(2)(g\circ f)(2)와 (f∘g)(2)(f\circ g)(2)를 계산하세요.

해설 보기

(g∘f)(2)=g(f(2))=g(4)=5(g\circ f)(2)=g(f(2))=g(4)=5이고, (f∘g)(2)=f(g(2))=f(3)=9(f\circ g)(2)=f(g(2))=f(3)=9입니다. 중간값 4와 3을 따로 적어 두면, 합성 기호를 곱셈처럼 읽거나 순서를 뒤바꾸는 실수를 피할 수 있습니다. 순서를 바꾸면 결과가 달라지는 또 하나의 예입니다.

2. 바닥과 천장

⌊−2.7⌋\lfloor-2.7\rfloor와 ⌈−2.7⌉\lceil-2.7\rceil를 구하고, 항목 47개를 한 페이지에 10개씩 보여 줄 때 필요한 페이지 수를 구하세요.

해설 보기

-2.7 이하의 가장 큰 정수는 -3이고, -2.7 이상의 가장 작은 정수는 -2입니다. 페이지는 마지막 7개 항목도 보여야 하므로 ⌈47/10⌉=5\lceil47/10\rceil=5쪽이 필요합니다. 바닥 함수를 써서 4쪽이라고 하면 마지막 7개가 빠집니다. 정수로만 계산하려면 Python에서 -(-47 // 10)처럼 쓰면 5가 나옵니다.

3. 특성함수의 합

본문의 U={1,2,3,4}U=\{1,2,3,4\}, A={2,4}A=\{2,4\}, B={1,2}B=\{1,2\}에서 x = 2와 x = 3에 대해 χA(x)\chi_A(x), χB(x)\chi_B(x), χA∪B(x)\chi_{A\cup B}(x)를 구하세요. x = 2에서 두 특성함수를 그냥 더하면 무엇이 틀리나요?

해설 보기

2는 A와 B에 모두 속하므로 세 값이 모두 1입니다. 그냥 더하면 1 + 1 = 2가 되어, 0이나 1만 나와야 하는 특성함수가 아닙니다. 겹친 만큼을 빼는 식으로는 1 + 1 - 1·1 = 1이 되어 맞습니다. 3은 두 집합 모두에 속하지 않으므로 세 값이 모두 0이고, 식으로도 0 + 0 - 0·0 = 0입니다. 포함 여부를 나타내는 0과 1을 개수처럼 더하면 겹친 원소를 두 번 세게 됩니다.

중간값의 집합이 맞지 않으면 정의역과 공역부터 확인해 보세요. 다음 장의 행렬에서는 수를 표로 늘어놓고 계산하는 방법을 다룹니다.