다차원 데이터 구조 설계
Guava의 Table은 기본적으로 행과 열로 구성된 2차원 자료구조입니다. 하지만 실무에서는 날짜, 지역, 카테고리, 지표 등 여러 축을 동시에 다루는 다차원 데이터를 자주 마주하게 됩니다. 이 경우 Table을 중첩하거나 다른 컬렉션과 조합하여 고차원 구조를 모방할 수 있습니다.
중첩 Table로 3차원 구현
가장 직관적인 방법은 Table의 값 타입을 또 다른 Table으로 선언하는 것입니다. 예를 들어 시점-지점-항목이라는 세 축을 표현하려면 다음과 같이 구성합니다.
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.junit.jupiter.api.Test;
public class TripleAxisTableTest {
@Test
void demonstrateNestedStructure() {
// 시점(행) × 지점(열) → 항목-수치 Table
Table<String, String, Table<String, Double>> timeLocationMetrics = HashBasedTable.create();
// 첫 번째 시점-지점 조합의 데이터 생성
Table<String, Double> seoulMetrics = HashBasedTable.create();
seoulMetrics.put("매출액", 1250.5);
seoulMetrics.put("영업이익", 180.3);
// 두 번째 시점-지점 조합의 데이터 생성
Table<String, Double> busanMetrics = HashBasedTable.create();
busanMetrics.put("매출액", 890.2);
busanMetrics.put("영업이익", 95.7);
// 상위 Table에 등록
timeLocationMetrics.put("2024-Q3", "서울", seoulMetrics);
timeLocationMetrics.put("2024-Q3", "부산", busanMetrics);
// 계층적 조회
Table<String, Double> retrieved = timeLocationMetrics.get("2024-Q3", "서울");
Double revenue = retrieved.get("매출액");
System.out.printf("2024년 3분기 서울 매출: %.1f%n", revenue);
}
}
이 패턴의 핵심은 타입 파라미터를 통해 각 계층의 역할을 명확히 구분하는 것입니다. Table<R, C, Table<R2, C2, V>> 형태로 선언하면 직관적인 API 체인을 유지할 수 있습니다.
다중 루프를 통한 전체 순회
중첩된 구조에서 모든 데이터를 탐색하려면 각 계층별로 키 집합을 얻어 반복해야 합니다.
@Test
void traverseAllDimensions() {
Table<String, String, Table<String, Double>> dataCube = buildSampleData();
for (String period : dataCube.rowKeySet()) {
for (String branch : dataCube.columnKeySet()) {
Table<String, Double> metrics = dataCube.get(period, branch);
if (metrics == null) continue;
System.out.printf("[%s / %s]%n", period, branch);
metrics.cellSet().forEach(cell ->
System.out.printf(" %s: %.2f%n", cell.getRowKey(), cell.getValue())
);
}
}
}
cellSet()을 활용하면 내부 Table의 행-값 쌍을 효율적으로 순회할 수 있습니다. null 크는 반드시 필요하며, 특히 희소(sparse)한 데이터에서는 빈 셀이 많이 발생할 수 있습니다.
Map과 조합한 4차원 이상 확장
추가 축이 필요하다면 Map을 외부에 배치하여 차원을 확장합니다. 아래는 연도-월-지역-지표라는 4차원 구조를 구현한 예시입니다.
import java.util.HashMap;
import java.util.Map;
public class HyperCubeTableTest {
@Test
void fourDimensionalLookup() {
// 연도 → (월 × 지역 → 지표-값 Table)
Map<Integer, Table<String, String, Table<String, Integer>>> annualData = new HashMap<>();
// 최하위: 지표 측정값
Table<String, Integer> measurements = HashBasedTable.create();
measurements.put("방문자수", 15420);
measurements.put("전환율", 340);
// 중간: 월-지역 매핑
Table<String, String, Table<String, Integer>> monthlyRegional = HashBasedTable.create();
monthlyRegional.put("3월", "강남", measurements);
// 최상위: 연도 인덱싱
annualData.put(2024, monthlyRegional);
// 역순으로 조회
Integer visitors = annualData.get(2024)
.get("3월", "강남")
.get("방문자수");
System.out.println("2024년 3월 강남 방문자수: " + visitors);
}
}
이 방식의 장점은 각 계층마다 적합한 자료구조를 선택할 수 있다는 점입니다. 예를 들어 연도 축은 Map으로, 월-지역은 Table로, 지표-값은 또 다른 Table로 구성하여 접근 패턴에 맞게 최적화할 수 있습니다.
설계 시 고려사항
- 타입 안전성: 깊게 중첩될수록 제네릭 선언이 복잡해지므로, 별도의 래퍼 클래스나 타입 별칭을 고려하세요.
- 메모리 오버헤드: Guava Table은 편의성을 위해 추가 객체를 생성하므로, 대용량 데이터에서는 직접 배열 기반 구조를 검토해야 합니다.
- 일관성 없는 키: 각 계층의 키 타입이 달라도 무방하며, 이는 도메인 개념을 자연스럽게 반영할 수 있게 합니다.