본문으로 건너뛰기
Varlig 가이드
Varlig 홈

벡터와 행렬

벡터는 공간의 방향이나 여러 양의 묶음처럼 숫자를 한 줄로 늘어놓은 목록이에요. 행렬은 행으로 이루어진 목록이에요. 이 페이지에서는 내적과 외적, 행렬 곱셈, 행렬식과 역행렬, 여러 일차방정식을 한꺼번에 푸는 방법, 그리고 고윳값과 QR·LU 분해, 특잇값 분해 같은 선형대수의 수치 도구를 다뤄요.

선형대수 과제, 기하와 변환, 또는 여러 미지수가 일차방정식으로 서로 얽힌 문제를 풀 때 참고하세요. 벡터와 행렬은 일반 목록이므로 인덱스를 비롯해 목록과 통계에서 다룬 내용이 모두 그대로 적용돼요.

벡터

calc
dot([1, 2, 3], [4, 5, 6]) 결과 32
dot([3, 4], [4, -3]) 결과 0
cross([1, 0, 0], [0, 1, 0]) 결과 [0, 0, 1]
cross([1, 2, 3], [4, 5, 6]) 결과 [-3, 6, -3]
v = [3, 4]
sqrt(dot(v, v)) 결과 5

dot은 같은 위치의 요소끼리 곱해서 더해요. 내적이 0이면 [3, 4][4, -3]처럼 두 벡터가 서로 직각이라는 뜻이에요. cross는 두 입력 벡터 모두에 직각인 벡터를 구해요. x 방향과 y 방향의 외적은 z 방향이에요. 벡터의 길이는 자기 자신과의 내적의 제곱근이에요.

dot에는 길이가 같은 두 벡터가, cross에는 요소가 세 개인 두 벡터가 필요해요. 벡터를 더하거나 크기를 바꾸려면 일반 목록 산술을 사용하세요. [1, 2] + [3, 4][4, 6]이고 2 * [3, 4][6, 8]이에요.

행렬 쓰기와 곱하기

행렬은 행의 목록으로 쓰고, 각 행을 각각의 대괄호로 감싸세요.

calc
shape([[1, 2, 3], [4, 5, 6]]) 결과 [2, 3]
transpose([[1, 2, 3], [4, 5, 6]]) 결과 [[1, 4], [2, 5], [3, 6]]
identity(3) 결과 [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
[[1, 2], [3, 4]] * [[5, 6], [7, 8]] 결과 [[19, 22], [43, 50]]

shape[rows, columns](행 수, 열 수)를 알려 줘요. transpose는 행과 열을 바꿔요. identity(n)은 다른 행렬에 곱해도 그 행렬을 바꾸지 않는 n×n 행렬이에요.

두 행렬 사이의 *는 행렬 곱셈이며, 왼쪽 행렬의 열 수가 오른쪽 행렬의 행 수와 같아야 해요. 행렬에 벡터를 곱하려면 벡터를 한 행에 요소 하나씩 열로 쓰세요.

calc
# 점 (3, 1)을 반시계 방향으로 90° 회전
회전 = [[0, -1], [1, 0]]
회전 * [[3], [1]] 결과 [[-1], [3]]
회전 * 회전 결과 [[-1, 0], [0, -1]]

90°씩 두 번 회전하면 반 바퀴가 되므로 회전 * 회전은 두 부호를 모두 뒤집어요. [3, 1] 같은 평평한 목록은 행렬의 오른쪽에 올 수 없어요. 평평한 목록을 이름에 저장해 두었다면 transpose([v])로 열로 바꿀 수 있어요.

모양이 같은 행렬끼리는 다른 목록처럼 더하고 뺄 수 있고, 숫자를 곱하거나 나눌 수도 있어요.

모든 행렬 연산에는 몇 가지 규칙이 적용돼요.

  • 모든 행의 요소 개수가 같아야 해요.
  • 요소는 일반 숫자여야 해요. 단위와 금액은 받지 않아요.
  • 행렬은 최대 32행 32열까지 가능해요.
  • 세미콜론 줄임 표기는 없어요. [1, 2; 3, 4]가 아니라 [[1, 2], [3, 4]]로 쓰세요.

행렬식, 역행렬, 대각합, 계수

calc
M = [[2, 1], [5, 3]]
det(M) 결과 1
inverse(M) 결과 [[3, -1], [-5, 2]]
M * inverse(M) 결과 [[1, 0], [0, 1]]
trace(M) 결과 5
inverse([[2, 1], [1, 3]]) 결과 [[0.6, -0.2], [-0.2, 0.4]]
det([[1, 2], [2, 4]]) 결과 0
rank([[1, 2, 3], [2, 4, 6]]) 결과 1

det, inverse, trace(대각선 요소의 합)에는 정사각행렬이 필요해요. 행렬식이 0이면 그 행렬은 특이 행렬이에요. 행들이 서로 독립적이지 않아서 역행렬이 없으므로 inverse([[1, 2], [2, 4]])는 오류가 나요. rank는 독립적인 행이 몇 개인지 세며, 정사각행렬이 아닌 행렬도 받아요. 마지막 줄에서는 두 번째 행이 첫 번째 행의 두 배이므로 계수가 1이에요.

이 네 가지는 소수가 아니라 정확한 분수로 계산해요. 그래서 M * inverse(M)이 정확히 단위행렬로 나와요.

연립일차방정식 풀기

커피 세 잔과 차 두 잔이 ₩20,500이고, 커피 한 잔과 차 네 잔이 ₩18,500이라고 해 볼게요. 방정식마다 각 음료의 개수를 열 순서(커피, 차)를 정해서 한 행에 쓰고, 합계는 순서를 맞춘 목록에 넣으세요.

calc
solve_system([[3, 2], [1, 4]], [20500, 18500]) 결과 [4,500, 3,500]

결과는 열과 같은 순서로 나와요. 커피 한 잔은 ₩4,500, 차 한 잔은 ₩3,500이에요. 행렬 요소는 일반 숫자여야 하므로 통화 기호는 빼고 쓰세요.

solve_system(A, b)A * x = bx에 대해 풀어요. 미지수마다 한 행씩인 정사각행렬과, 길이가 같은 우변 값의 평평한 목록이 필요해요. 정확한 분수로 계산하므로 결과에 반올림 오차가 없어요.

방정식으로 답이 하나로 정해지지 않으면 해가 유일하지 않다는 오류가 표시돼요. 예를 들어 solve_system([[1, 2], [2, 4]], [3, 6])은 두 번째 방정식이 첫 번째 방정식을 두 배 한 것이라서 실패해요. solve_system은 일차(선형) 방정식만 풀 수 있어요. x^2 = 2 같은 방정식에는 합, 방정식 풀이, 미적분solve를 사용하세요.

고윳값

calc
eigenvalues([[2, 1], [1, 2]]) 결과 [1, 3]
eigenvalues([[4, 1], [2, 3]]) 결과 [5, 2]
eigenvalues([[0, -1], [1, 0]]) 결과 [i, -i]

eigenvalues는 정사각행렬을 받아 평평한 목록을 돌려줘요. 90° 회전은 어떤 방향도 그대로 두지 않으므로 고윳값이 복소수 쌍 i-i예요. 복소수를 참고하세요. 목록의 순서에는 의존하지 마세요.

분해: QR, LU, SVD

각 분해는 행렬의 목록을 돌려줘요. 인덱스로 각 부분을 꺼내세요.

calc
A = [[3, 0], [4, 5]]
인수 = qr(A)
q = 인수[0] 결과 [[0.6, -0.8], [0.8, 0.6]]
r = 인수[1] 결과 [[5, 4], [0, 3]]
q * r 결과 [[3, 0], [4, 5]]

인수들을 다시 곱하면 A가 나오므로 분해가 맞는지 한눈에 확인할 수 있어요. 이진 부동소수점으로 계산하면 10⁻¹⁵ 정도의 아주 작은 반올림 찌꺼기가 남는데, Varlig는 목록과 행렬에서 이를 0으로 표시해요. 저장된 값에는 모든 자릿수가 남아 있으므로 분해 결과는 ==가 아니라 눈으로 보거나 차이를 구해서 비교하세요.

calc
lu([[2, 1], [4, 3]]) 결과 [[[0, 1], [1, 0]], [[1, 0], [0.5, 1]], [[4, 3], [0, -0.5]]]

lu[P, L, U]를 돌려줘요. P는 행 교환을 기록하며 P * AL * U와 같아요. 이 예에서는 더 큰 수인 4를 먼저 쓰도록 행이 교환되었어요.

calc
B = [[3, 1], [4, 7]]
f = svd(B)
u = f[0]
s = f[1]
vt = f[2]
u * [[s[0], 0], [0, s[1]]] * vt 결과 [[3, 1], [4, 7]]
svd([[3, 4]]) 결과 [[[1]], [5], [[0.6, 0.8]]]

svd[U, s, Vt]를 돌려주며, s는 특잇값을 큰 순서대로 담은 평평한 목록이에요. 교과서에서는 BU * diag(s) * Vt와 같다고 써요. diag 함수는 없으므로 [[s[0], 0], [0, s[1]]]처럼 대각행렬을 직접 만드세요.

함수 필요한 입력과 결과
identity(n) n×n 단위행렬
shape(A) [rows, columns](행 수, 열 수)
transpose(A) 행과 열을 바꾼 행렬
det(A) 정사각행렬의 행렬식
inverse(A) 행렬식이 0이 아닌 정사각행렬의 역행렬
trace(A) 정사각행렬의 대각선 요소의 합
rank(A) 독립적인 행의 개수(모든 직사각행렬 가능)
solve_system(A,b) 해가 하나인 정사각행렬에 대한 A * x = b의 해
eigenvalues(A) 정사각행렬의 고윳값(복소수일 수 있으며 순서는 정해져 있지 않음)
qr(A) Q * RA와 근사적으로 같은 [Q, R](모든 직사각행렬 가능)
lu(A) P * AL * U와 근사적으로 같은 [P, L, U](정사각행렬)
svd(A) s가 평평한 목록인 [U, s, Vt](모든 직사각행렬 가능)

정사각행렬이 아닌 행렬에 대해 qrsvd는 축소된 인수를 돌려줘요. 3×2 행렬이라면 Q도 3×3이 아니라 3×2예요.

알고리즘과 한계

det, inverse, rank, solve_system은 계산기가 다룰 수 있는 숫자 크기의 한계 안에서 정확한 분수로 계산해요. eigenvalues, qr, lu, svd는 이진 부동소수점으로 계산하므로 결과는 매우 가까운 근삿값이에요. 반복 계산은 10,000단계가 지나면 멈춰요.

[[1, 1], [1, 1.0000001]]처럼 행들이 거의 종속적인 행렬을 조건수가 나쁜 행렬이라고 해요. 이런 행렬로 계산하면 작은 반올림 오차가 크게 불어나므로 분해와 고윳값의 정확도가 떨어질 수 있고, 아주 극단적인 경우에는 수치 한계를 넘어 오류가 날 수 있어요.

전체 규칙은 고급 수학 참조에 있어요.

종합해 보기

시장 노점에서 받은 영수증 세 장에는 사과, 바나나, 오렌지를 몇 개씩 샀고 모두 얼마를 냈는지는 적혀 있지만, 각각의 가격은 적혀 있지 않아요.

calc
# 각 행: 사과, 바나나, 오렌지
장바구니 = [[2, 3, 1], [1, 2, 3], [4, 1, 2]]
지불액 = [4300, 5400, 6100]
det(장바구니) 결과 25
가격 = solve_system(장바구니, 지불액) 결과 [800, 500, 1,200]
# 모든 영수증과 가격이 맞는지 확인
장바구니 * transpose([가격]) 결과 [[4,300], [5,400], [6,100]]
# 사과 5개, 바나나 2개, 오렌지 4개라면 얼마일까?
dot([5, 2, 4], 가격) 결과 9,800

행렬식이 0이 아니므로 영수증만으로 가격을 한 가지로 정할 수 있어요. 사과는 ₩800, 바나나는 ₩500, 오렌지는 ₩1,200이에요. 장바구니에 가격을 열로 바꿔 곱하면 합계가 그대로 다시 나오고, dot으로 새 장바구니의 가격을 계산하면 ₩9,800이에요.