Lua 테이블 순회와 삭제 전략

순회 방식 비교

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 할당 맵/해시 요소 제거 배열 연속성 미보장

태그: Lua ipairs pairs table.remove 배열 순회

5월 25일 19:23에 게시됨