배열에서 중복 값을 제거하려고 array_unique()를 썼는데, 예상과 다르게 중복이 남아있거나 필요한 값까지 사라져버린 경험이 있을까? 대부분의 개발자는 array_unique()가 단순히 '같은 값'을 찾아 제거하는 함수라고 생각하지만, 실제로는 타입 비교 방식에 따라 결과가 크게 달라진다. 이번에는 array_unique()가 정확히 뭔지, 왜 예상과 다른 결과가 나오는지, 어떻게 올바르게 사용하는지 완벽하게 정리해서 소개하겠다.
array_unique()는 배열에서 중복된 값을 제거하는 기본 함수다. 첫 번째로 나타난 값은 유지하고, 그 이후 중복된 값들을 삭제한다. 다만 여기서 중요한 점은 '어떻게 비교하는가'다.
기본 동작
array_unique()는 기본적으로 느슨한 타입 비교(loose comparison)를 사용한다. 즉, 정수 1과 문자열 "1"을 같은 값으로 본다는 뜻이다. 이는 PHP의 Type Juggling 특성 때문이다.
<?php
$array = [1, "1", 2, "2", 1, "1"];
$result = array_unique($array);
print_r($result);
?>
예상되는 결과: 배열 [1, 2] (정수만 남음)
실제 결과:
Array
(
[0] => 1
[2] => 2
[4] => 1 // 이건 문자열 "1"인데 출력에선 구분 안 됨
)
보이는 값은 같아 보이지만, 키와 순서에서 차이가 난다. 정수 1이 [0]과 [4] 두 위치에 남아있다.
array_unique()는 네 번째 파라미터로 비교 모드를 지정할 수 있다.
array_unique(array $array, int $flags = SORT_STRING): array
$flags 옵션
| 플래그 | 설명 | 비교 방식 |
|---|---|---|
| SORT_REGULAR(기본값) | 정규 비교 | 느슨한 타입 비교 (==), 1 == "1" |
| SORT_NUMERIC | 숫자로 비교 | 숫자 값 기준, 1과 "1" 같음 |
| SORT_STRING | 문자열로 비교 | 문자열 기준, "1"과 1 다름 |
잘못된 코드 (✗)
<?php
$array = [1, "1", 2, "2"];
$result = array_unique($array); // SORT_REGULAR 기본 적용
print_r($result);
// 결과: [0 => 1, 2 => 2] - "1"과 "2"는 제거됨
?>
이 코드는 개발자 의도를 불명확하게 한다. 숫자와 문자열이 섞여 있을 때 어떤 비교를 원하는지 명시하지 않았기 때문이다.
올바른 코드 (✓) - 문자열 기반 비교
<?php
$array = [1, "1", 2, "2"];
$result = array_unique($array, SORT_STRING);
print_r($result);
// 결과: [0 => 1, 1 => "1", 2 => 2, 3 => "2"] - 아무것도 제거 안 됨
?>
SORT_STRING을 지정하면 정수 1과 문자열 "1"을 다른 값으로 취급한다. 따라서 모든 값이 유지된다.
올바른 코드 (✓) - 숫자 기반 비교
<?php
$array = [1, "1", 2, "2", 1];
$result = array_unique($array, SORT_NUMERIC);
print_r($result);
// 결과: [0 => 1, 2 => 2] - "1"과 "2"는 제거됨
?>
이제 명확하게 숫자 값 기준으로 비교했으므로, 의도가 드러난다.
사례 1: CSV 파일에서 읽은 데이터
<?php
// CSV에서 읽으면 모든 값이 문자열
$imported_ids = ["123", "456", "123", "789"];
// 잘못된 방법: SORT_REGULAR 사용
$wrong = array_unique($imported_ids);
// 결과: [0 => "123", 1 => "456", 3 => "789"] - 중복 제거 잘됨
// 하지만 데이터베이스의 정수 ID와 비교할 때
$db_ids = [123, 456, 789];
$wrong_result = array_diff($db_ids, $wrong);
// 결과: [0 => 123, 1 => 456, 2 => 789] - SORT_REGULAR 비교 때문에 예상 결과와 다를 수 있음
// 올바른 방법: 명시적으로 타입 변환
$clean_ids = array_map('intval', $imported_ids);
$result = array_unique($clean_ids, SORT_NUMERIC);
print_r($result);
// 결과: [0 => 123, 1 => 456, 3 => 789]
?>
사례 2: 연관 배열에서 특정 필드 중복 제거
<?php
$users = [
['id' => 1, 'email' => 'john@example.com'],
['id' => 2, 'email' => 'jane@example.com'],
['id' => 1, 'email' => 'john@example.com'], // 중복
];
// ✗ array_unique()로 연관 배열 전체를 비교하려고 하면 실패
$wrong = array_unique($users);
// 결과: 모든 배열 요소가 유지됨 (배열은 직렬화되어 비교됨)
// ✓ 특정 필드만 추출해서 중복 제거
$seen_ids = [];
$unique_users = [];
foreach ($users as $user) {
if (!in_array($user['id'], $seen_ids, true)) {
$seen_ids[] = $user['id'];
$unique_users[] = $user;
}
}
print_r($unique_users);
// 결과: 두 개의 사용자만 출력
?>
사례 3: 부동소수점 숫자의 함정
<?php
$prices = [1.5, "1.5", 1.50, "1.50"];
// ✗ SORT_NUMERIC으로 비교
$wrong = array_unique($prices, SORT_NUMERIC);
print_r($wrong);
// 결과: [0 => 1.5, 2 => 1.5]
// "1.5"와 "1.50"은 문자열로는 다르지만, SORT_NUMERIC으로는 같음
// ✓ 명시적으로 타입 강제 변환
$clean = array_map('floatval', $prices);
$result = array_unique($clean, SORT_NUMERIC);
print_r($result);
// 결과: [0 => 1.5] - 확실히 하나만 남음
?>
✗ 잘못된 것: 깊은 배열에 array_unique() 직접 적용
<?php
// 다차원 배열
$data = [
[1, 2],
[1, 2],
[2, 3]
];
$result = array_unique($data);
// 배열은 직렬화되어 비교되므로 예상과 다를 수 있음
print_r($result);
?>
✓ 올바른 것: JSON 직렬화 후 비교
<?php
$data = [
[1, 2],
[1, 2],
[2, 3]
];
// 배열을 JSON 문자열로 변환해서 비교
$result = array_unique(array_map('json_encode', $data));
$result = array_map('json_decode', $result, [true]);
print_r($result);
// 결과: [[1, 2], [2, 3]]
?>
✗ 잘못된 것: 키 유지를 간과한 경우
<?php
$array = ['a' => 1, 'b' => 2, 'c' => 1];
$result = array_unique($array);
print_r($result);
// 결과: ['a' => 1, 'b' => 2] - 원본 키가 유지됨
// 이를 배열로 변환하려면 array_values() 필요
?>
✓ 올바른 것: 키 초기화까지 완료
<?php
$array = ['a' => 1, 'b' => 2, 'c' => 1];
$result = array_values(array_unique($array));
print_r($result);
// 결과: [0 => 1, 1 => 2] - 키가 0부터 시작하도록 초기화됨
?>
array_unique()는 배열 중복 제거의 기본 도구지만, 타입 비교 방식을 제어하지 못하면 예상과 다른 결과를 얻는다. 정수와 문자열이 섞여 있을 때는 SORT_STRING으로 명시하고, 연관 배열이나 다차원 배열은 상황에 맞춰 다른 접근을 택해야 한다. 작은 타입 체크 습관이 모여서 데이터 손실 없는 안정적인 배열 처리를 만든다는 점을 잊지 말자. 이 글의 플래그 선택 부분을 참고해 현재 프로젝트의 array_unique() 호출마다 의도를 명시하면, 데이터 무결성 문제를 미연에 방지할 수 있을 것이다.