실무에서 배열을 다루다 보면 같은 값이 여러 번 반복해서 등장하는 경우가 자주 있습니다. 예를 들어 다음과 같은 숫자 배열이 있다고 가정해 보겠습니다.
const arr = [1, 4, 3, 3, 1, 3, 2, 4, 2, 1, 4, 4];
여기서 우리가 만들 함수의 목표는 명확합니다. 원본 배열에서 두 번 이상 등장한 값(중복 값)만 골라내되, 결과 배열에는 각 값을 딱 한 번만 포함하는 것입니다.
위 배열의 경우 1, 4, 3, 2 네 개의 값이 모두 여러 번 나타나므로, 기대하는 출력 결과는 다음과 같습니다.
const output = [1, 4, 3, 2];
해결 아이디어
핵심 로직은 간단합니다. 배열의 각 요소에 대해 다음 두 가지를 확인하면 됩니다.
- 중복 여부 판별:
indexOf()와lastIndexOf()의 반환값이 서로 다르다면, 해당 값이 배열 안에 최소 두 번 이상 존재한다는 의미입니다. - 결과 중복 방지: 이미 결과 배열에 추가된 값인지
includes()로 검사하여, 같은 값이 여러 번 push 되지 않도록 합니다.
코드 구현
위 아이디어를 바탕으로 작성한 코드는 다음과 같습니다.
const arr = [1, 4, 3, 3, 1, 3, 2, 4, 2, 1, 4, 4];
const pickDuplicate = arr => {
const res = [];
for(let i = 0; i < arr.length; i++){
// indexOf와 lastIndexOf가 다르면 중복 값
if(arr.indexOf(arr[i]) !== arr.lastIndexOf(arr[i])){
// 아직 결과에 없는 값만 추가
if(!res.includes(arr[i])){
res.push(arr[i]);
}
}
}
return res;
};
console.log(pickDuplicate(arr));실행 결과
콘솔에 출력되는 결과는 다음과 같습니다.
[1, 4, 3, 2]
동작 원리 살펴보기
코드가 어떻게 동작하는지 단계별로 정리하면 다음과 같습니다.
- 배열을 처음부터 끝까지 순회하면서 각 요소를 하나씩 확인합니다.
arr.indexOf(arr[i])는 해당 값이 처음 등장하는 위치를,arr.lastIndexOf(arr[i])는 마지막으로 등장하는 위치를 반환합니다.- 두 위치가 다르면 그 값은 배열 내에서 최소 두 번 이상 등장한 중복 값입니다.
- 중복 값이라면,
res.includes()로 이미 결과 배열에 담겼는지 확인한 뒤 없을 때만 추가합니다.
참고: Set을 활용한 대안
ES6의 Set 객체를 활용하면 코드를 더 간결하게 만들 수 있습니다.
const pickDuplicate = arr => {
const seen = new Set();
const res = new Set();
for(const num of arr){
if(seen.has(num)){
res.add(num);
} else {
seen.add(num);
}
 }
return [...res];
};
console.log(pickDuplicate([1, 4, 3, 3, 1, 3, 2, 4, 2, 1, 4, 4]));
// [1, 4, 3, 2]이 방식은 Set의 조회 성능이 O(1)이기 때문에, indexOf/includes를 사용하는 첫 번째 방법(O(n²))보다 배열이 클 때 훨씬 효율적입니다. 상황에 맞게 적절한 방법을 선택하시기 바랍니다.