Làm thế nào loại bỏ phần tử trùng lặp khỏi array trong Javascript

Hôm nay mình xin giới thiệu đến các bạn 3 cách loại bỏ phần tử trùng lặp và chỉ giữ lại những giá trị duy nhất trong mảng của Javascript. Okie, let’s go.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var array = [1, 2, 3, 1, 4, 2, 5];

// 1. Filter
var arrFilter = array.filter((item, index) => array.indexOf(item) === index);
// viết kiểu ES5
var arrFilter = array.filter(function (item, index) {
return array.indexOf(item) === index;
});

// 2. Reduce
var arrReduce = array.reduce(
(accumulator, currentItem) => accumulator.includes(currentItem) ? accumulator : [...accumulator, currentItem],
[]
);
// viết kiểu ES5
var arrReduce = array.reduce(
function (accumulator, currentItem) {
if (accumulator.includes(currentItem)) {
return accumulator;
}

return [...accumulator, currentItem];
},
[]
);

// 3. Set
var arrSet = [...new Set(array)];

console.log(arrFilter);
console.log(arrReduce);
console.log(arrSet);

// Kết quả:
// [1, 2, 3, 4, 5]
// [1, 2, 3, 4, 5]
// [1, 2, 3, 4, 5]

Sử dụng filter

Hàm filter sẽ tạo ra một mảng mới với kết quả đã được “chọn lọc” từ điều kiện chúng ta đưa vào. Nói cách dễ hiểu, nếu giá trị chúng ta thỏa điều kiện, filter sẽ “cho phép” thêm phần tử hiện tại này vào mảng mới để trả về, ngược lại không thỏa điều kiện filter sẽ bỏ qua phần tử đó.

Lợi dụng điểm này chúng ta sẽ viết điều kiện sàng lọc phần tử trùng nhau để filter bỏ qua, và chỉ trả về mảng với giá trị duy nhất.

Điều kiện sàng lọc là một callback (callback này truyền vào hàm filter), trong callback này nếu thỏa điều kiện thì trả về true -> giữ giá trị hiện tại lại, false -> ngược lại.

Cùng nhìn qua ví dụ dưới đây để hiểu rõ hơn.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const array = [1, 2, 3, 1, 4, 2, 5];

array.filter((item, index) => {
// console này in ra để dễ hình dung thôi
console.log(
item, // giá trị hiện tại
index, // index hiện tại
array.indexOf(item), // index của giá trị hiện tại trong mảng là bao nhiêu
array.indexOf(item) === index // điều kiện sàng lọc
);

// đây là điều kiện sàng lọc, kiểm tra điều kiện và trả về `true` hoặc `false`
// trả về `true` sẽ cho `item` vào mảng mới để trả về
return array.indexOf(item) === index;
});

// [1, 2, 3, 4, 5]

Ảnh bên dưới là kết quả console in ra, bạn chỉ cần nhìn sơ qua thôi, nếu chưa hiểu thì hãy đọc tiếp rồi nhìn lại thì bạn sẽ hiểu nhé :v, mình chắc luôn.

image

Ở chỗ này chúng ta lợi dụng cơ chế làm việc của indexOf để kiểm tra xem phần tử đó có trùng lặp hay không. Cơ chế hoạt động của indexOf như sau:

  1. Bạn truyền vào một giá trị để tìm index
  2. indexOf lặp hết mảng đó và sẽ trả về index đầu tiên mà nó “kiếm” được trong mảng.

Suy ra nếu index hiện tại (index trong callback của filter) mà không khớp với index trong mảng (dùng indexOf) thì giá trị đó đã bị lặp lại.

Các bước sẽ như sau:

image

Sử dụng reduce

Định nghĩa từ MDN về reduce()

The reduce() method executes a reducer function (that you provide) on each member of the array resulting in a single output value.

Về mục đích hàm reduce thì nó sẽ giảm dần (thực chất là chạy vòng lặp) các phần tử trong mảng dựa trên callback truyền vào, callback này thường gọi là reducer (dịch ra là hàm giảm). Callback này trả về một kết quả duy nhất. Mình có một bài viết giải thích về hàm này hoạt động tại đây

Thực chất callback reducer giống như cái điều kiện, khi phần tử nào vượt qua được điều kiện nó sẽ cộng dồn vào, để cuối cùng trả về cái đã cộng dồn này. Cộng dồn vào cái gì? Các bạn có thấy tham số thứ hai của hàm reduce không, mảng rỗng đó là giá trị ban đầu để truyền vào callback reducer, tham số nhận giá trị ban đầu này tên là accumulator, khi thỏa nó sẽ push vào accumulator, khi lặp xong nó sẽ trả về mảng đã cộng dồn xong.

Giải bài toán loại bỏ phần tử trùng lặp trong mảng này, chúng ta sẽ sử dụng callback reducer với 2 tham số đặt tên là accumulator (đây mới chính là mảng để cộng dồn kết quả vào) và currentItem biến này chứa item hiện tại. Nếu item hiện tại chưa có trong mảng accumulator thì thêm vào accumulator có rồi thì không thêm.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var array = [1, 2, 3, 1, 4, 2, 5];

array.reduce((accumulator, currentItem) => {
console.log(
// Giá trị hiện tại
currentItem,
// Đây gọi là tham số tích trữ (thuật ngữ là `accumulator`)
accumulator,
// Điều kiện để thêm vào tích trữ là không được chứa giá trị này trước đó, nghĩa là `false`
accumulator.includes(currentItem),
// Đây là `reducer`
accumulator.includes(currentItem) ? accumulator : [...accumulator, currentItem]
);

return accumulator.includes(currentItem) ? accumulator : [...accumulator, currentItem];
}, []); // Giá trị mặc định của tham số tích trữ `accumulateUnique` là mảng trống, tích trữ giá trị thỏa điêu kiện vào đó

// [1, 2, 3, 4, 5]

Step by step:

  • Reduce nhận 2 tham số: một là callback, hai là giá trị khởi tạo để truyền vào callback
  • Khi lặp, gọi callback truyền vào giá trị khỏi tạo, accumulator lúc này sẽ là mảng rỗng.
  • Check nếu accumulator chứa currentItem (accumulator.includes(currentItem)) thì không thêm currentItem vào nữa chỉ trả về mảng accumulator thôi. Ngược lại thêm vào mảng accumulator, cú pháp thêm vào [...accumulator, currentItem].
  • Cứ như vậy lặp hết mảng sẽ trả về được mảng duy nhất không bị lặp phần tử nào.

Đây là kết quả in ra dưới console

image

Sử dụng Set

Đây là cách hiện đại, cú pháp ngắn gọn, dễ nhớ. Set là một data Object mới được giới thiệu trong ES6. Với đặc tính chỉ chứa các giá trị duy nhất (không trùng lặp) và tự động loại bỏ những giá trị trùng lặp ra, khi cố tình truyền vào, nên chúng ta sẽ sử dụng nó để loại trừ phần tử bị trùng trong mảng.

1
2
3
4
var array = [1, 2, 3, 1, 4, 2, 5];

[...new Set(array)]
// [1, 2, 3, 4, 5]

Chúng ta sẽ cũng đi phân tích một chút về cách implement vào bài toán của chúng ta. Có thể thấy nó sẽ diễn ra với hai bước như sau:

  1. Khởi tạo một object Set với giá trị truyền vào constructor là một mảng. Bởi vì Set chỉ chứa giá trị duy nhất nên cơ chế của nó sẽ tự động loại bỏ những trùng lặp.
  2. Bây giờ chúng ta đã có một Set với tất cả trùng lặp đã biến mất, và sử dụng toán tử spread (…) để destructing nó ra mảng.

Cách viết chi tiết nhất có thể

1
2
3
4
5
6
var array = [1, 2, 3, 1, 4, 2, 5];

var uniqueSet = new Set(array);

var uniqueArray = [...uniqueSet];
// [1, 2, 3, 4, 5]

Ngoài ra thì chúng ta có thể thay thế toán tử spread bằng cách dùng prototype có sẵn của Array là Array.from

1
2
3
const array = [1, 2, 3, 1, 4, 2, 5];

Array.from(new Set(array)); // [1, 2, 3, 4, 5]

Kết luận

Bạn có thể tham khảo kết quả benchmark các cách trên để lựa chọn ra giải pháp ngon lành, phù hợp nhất nhé, link.

P/s: Với mình, mình ưng dùng Set nhất, vì cú pháp ngắn gọn nhìn đơn giản dễ nhớ và cộng thêm trong NodeJs mà dùng Set ngó rất đẹp :D.

Có góp ý hay điều gì đó comment cho mình biết nhá. Thanks đã đọc nè :P.

Updated date: 21.03.2020

Tham khảo

 Comments
Comment plugin failed to load
Loading comment plugin