순회 방식 비교
Lua 테이블을 순회하는 대표적인 네 가지 접근법을 살펴본다. 각 방식은 내부 메커니즘과 적용 상황이 다르다.
1. ipairs — 연속 정수 키 전용
인덱스 1부터 시작하여 1씩 증가하는 연속된 정수 키만 순회한다. 중간에 빈 틈이 발생하면 즉시 종료한다.
-- 예시: 정상 순회
local scores = { [1] = 85, [2] = 90, [3] = 78, [4] = 92 }
for idx, val in ipairs(scores) do
print(val) -- 85, 90, 78, 92
end
-- 예시: 중간에 빈 틀
local gaps = { [1] = "a", [2] = "b", [4] = "d" }
for idx, val in ipairs(gaps) do
print(val) -- "a", "b" (4번은 접근하지 않음)
end
-- 예시: 1번 키 부재
local offset = { [2] = "x", [3] = "y" }
for idx, val in ipairs(offset) do
print(val) -- 아무도 출력되지 않음
end
2. pairs — 전체 키-값 쌍
해시 기반 순서로 모든 쌍을 순회한다. 키의 종류와 순서에 무관하게 전체 데이터에 접근할 수 있다.
-- 예시: 순서 불확실성
local mixed = { [1] = 10, [3] = 30, [2] = 20, [5] = 50 }
for k, v in pairs(mixed) do
print(k, v) -- 출력 순서는 해시값에 의존
end
-- 예시: 문자열 키 포함
local config = { ["host"] = "localhost", ["port"] = 3306, ["timeout"] = 30 }
for k, v in pairs(config) do
print(k, v)
end
3. 길이 연산자 # — 배열 길이 기반
# 연산자는 연속된 정수 키의 개수를 반환하며, 이를 활용한 수동 순회가 가능하다.
-- 예시: 완전한 연속 배열
local complete = { "apple", "banana", "cherry" }
print(#complete) -- 3
for i = 1, #complete do
print(complete[i])
end
-- 예시: 비연속 배열
local sparse = { [1] = "a", [2] = "b", [5] = "e" }
print(#sparse) -- 2 (1,2까지만 연속으로 인식)
-- 예시: 1번 인덱스 없음
local shifted = { [2] = "second", [3] = "third" }
print(#shifted) -- 0
4. table.maxn — 최대 정수 키 탐색
테이블 내 존재하는 최대 정수 키를 반환한다. Lua 5.2부터는 제거되었으므로 호환성 주의가 필요하다.
local data = { [1] = 10, [3] = 30, ["label"] = "test", [6] = 60 }
-- 사용자 정의 구현 (5.2 이상)
local function findMaxKey(tbl)
local max = 0
for k, _ in pairs(tbl) do
if type(k) == "number" and k > max then
max = k
end
end
return max
end
print(findMaxKey(data)) -- 6
-- 주의: nil 값이 있는 인덱스도 순회 대상이 됨
for i = 1, findMaxKey(data) do
print(i, data[i]) -- 3번과 5번은 nil 출력
end
요소 삭제 기법
방법 1: table.remove — 배열 압축
지정 위치의 요소를 제거하고 후속 요소들을 앞으로 당긴다. 인덱스가 자동 재조정된다.
local queue = { "first", "second", "third", "fourth" }
-- 중간 요소 제거
table.remove(queue, 2) -- "second" 제거
for pos, item in ipairs(queue) do
print(pos, item) -- 1:first, 2:third, 3:fourth
end
방법 2: nil 할당 — 키 제거
특정 키에 nil을 할당하여 해당 쌍을 테이블에서 제거한다. 배열의 연속성을 해치지 않는다.
local lookup = {
uid_1001 = { name = "Alice", level = 25 },
uid_1002 = { name = "Bob", level = 30 },
uid_1003 = { name = "Carol", level = 28 }
}
-- 특정 항목 제거
lookup.uid_1002 = nil
for id, info in pairs(lookup) do
print(id, info.name)
end
순회 중 삭제 패턴
순회 중 요소를 안전하게 제거하려면 역방향 순회 또는 키 목록 분리 기법을 사용한다.
-- 역방향 순회로 안전한 제
local nums = { 10, 20, 30, 40, 50, 60 }
for i = #nums, 1, -1 do
if nums[i] > 30 then
table.remove(nums, i) -- 인덱스 재조정 영향 없음
end
end
-- 결과: { 10, 20, 30 }
-- 또는 키 수집 후 일괄 삭제
local registry = { a = 1, b = 2, c = 3, d = 4 }
local toErase = {}
for k, v in pairs(registry) do
if v % 2 == 0 then
table.insert(toErase, k)
end
end
for _, key in ipairs(toErase) do
registry[key] = nil
end
| 방식 | 적합한 상황 | 주의사항 |
|---|---|---|
| ipairs | 연속 인덱스 배열 | 빈 틈 발생 시 중단 |
| pairs | 모든 키-값 순회 | 순서 보장 없음 |
| # 연산자 | 길이 확정 배열 | 비연속 배열 시 부정확 |
| table.remove | 배열 요소 제거 | 인덱스 재조정 발생 |
| nil 할당 | 맵/해시 요소 제거 | 배열 연속성 미보장 |