Nhận tất cả các giá trị duy nhất trong một mảng JavaScript (loại bỏ trùng lặp)


1487

Tôi có một dãy số mà tôi cần phải chắc chắn là duy nhất. Tôi tìm thấy đoạn mã dưới đây trên internet và nó hoạt động rất tốt cho đến khi mảng có số 0 trong đó. Tôi đã tìm thấy đoạn script khác ở đây trên Stack Overflow trông gần giống hệt như nó, nhưng nó không thất bại.

Vì vậy, để giúp tôi học hỏi, ai đó có thể giúp tôi xác định kịch bản nguyên mẫu đang sai ở đâu không?

Array.prototype.getUnique = function() {
 var o = {}, a = [], i, e;
 for (i = 0; e = this[i]; i++) {o[e] = 1};
 for (e in o) {a.push (e)};
 return a;
}

Thêm câu trả lời từ câu hỏi trùng lặp:

Câu hỏi tương tự:


3
@hippietrail Câu hỏi cũ hơn đó là về việc tìm và trả lại các bản sao (tôi cũng bối rối!). Câu hỏi của tôi là nhiều hơn về lý do tại sao chức năng này thất bại khi một mảng có số 0 trong đó.
Mottie

Bạn có thể muốn làm cho tiêu đề câu hỏi của bạn ít mơ hồ quá.
hà mã

Đối với những người đọc trong tương lai, khi bắt đầu thấy rằng bạn phải sửa đổi thuật toán nội dung cấu trúc dữ liệu của mình mọi lúc, (sắp xếp chúng, loại bỏ các phần tử lặp lại, v.v.) hoặc tìm kiếm các phần tử bên trong nó ở mỗi lần lặp, sẽ an toàn khi cho rằng bạn Đang sử dụng cấu trúc dữ liệu sai ở vị trí đầu tiên và bắt đầu sử dụng cấu trúc phù hợp hơn cho nhiệm vụ trong tay (trong trường hợp này là tập băm thay vì mảng).
Nurettin

Tôi đã sao chép mã từ một nơi khác, một thời gian trước đây ... nhưng nó có vẻ khá đơn giản: o= object, a= array, i= indexe= umm, một cái gì đó: P
Mottie

Câu trả lời:


2661

Với JavaScript 1.6 / ECMAScript 5, bạn có thể sử dụng filterphương thức gốc của Mảng theo cách sau để có được một mảng với các giá trị duy nhất:

function onlyUnique(value, index, self) { 
    return self.indexOf(value) === index;
}

// usage example:
var a = ['a', 1, 'a', 2, '1'];
var unique = a.filter( onlyUnique ); // returns ['a', 1, 2, '1']

Phương thức gốc filtersẽ lặp qua mảng và chỉ để lại những mục vượt qua hàm gọi lại đã cho onlyUnique.

onlyUniquekiểm tra, nếu giá trị đã cho là lần đầu tiên xảy ra. Nếu không, nó phải là một bản sao và sẽ không được sao chép.

Giải pháp này hoạt động mà không cần bất kỳ thư viện bổ sung nào như jQuery hoặc nguyên mẫu.

Nó hoạt động cho các mảng với các loại giá trị hỗn hợp quá.

Đối với các Trình duyệt cũ (<eg9), không hỗ trợ các phương thức gốc filterindexOfbạn có thể tìm thấy các công việc xung quanh trong tài liệu MDN cho bộ lọcindexOf .

Nếu bạn muốn giữ lần xuất hiện cuối cùng của một giá trị, hãy thay thế đơn giản indexOfbằng lastIndexOf.

Với ES6, có thể rút ngắn điều này:

// usage example:
var myArray = ['a', 1, 'a', 2, '1'];
var unique = myArray.filter((v, i, a) => a.indexOf(v) === i); 

// unique is ['a', 1, 2, '1']

Cảm ơn Camilo Martin cho gợi ý trong nhận xét.

ES6 có một đối tượng riêng Setđể lưu trữ các giá trị duy nhất. Để có được một mảng với các giá trị duy nhất bạn có thể làm ngay bây giờ:

var myArray = ['a', 1, 'a', 2, '1'];

let unique = [...new Set(myArray)]; 

// unique is ['a', 1, 2, '1']

Hàm tạo của Setmột đối tượng có thể lặp lại, như Array và toán tử trải rộng ...biến đổi tập hợp trở lại thành một Array. Cảm ơn Lukas Liese cho gợi ý trong nhận xét.


54
Giải pháp này sẽ chạy chậm hơn nhiều, thật không may. Bạn đang lặp lại hai lần, một lần với bộ lọc và một lần với chỉ số của
Jack Franzen

19
@JackFranzen Chậm hơn gì? Giải pháp từ Rafael ? Giải pháp của Rafaels không hoạt động đối với các mảng kiểu hỗn hợp. Ví dụ của tôi, ['a', 1, 'a', 2, '1']bạn sẽ nhận được ['a', 1, 2]. Nhưng đây không phải là điều tôi mong đợi. BTW, chậm hơn nhiều là rất tương đối.
TLindig

25
Trong JS hiện đại: .filter((v,i,a)=>a.indexOf(v)==i)(ký hiệu mũi tên béo).
Camilo Martin

164
let unique_values = [...new Set(random_array)]; developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Khăn

5
Để có câu trả lời chi tiết hơn, bao gồm nhiều khả năng - chẳng hạn như sắp xếp trước và xử lý các loại dữ liệu khác nhau - xem stackoverflow.com/a/9229821/368896
Dan Nissenbaum

876

Câu trả lời được cập nhật cho ES6 / ES2015 : Sử dụng Bộ , giải pháp dòng đơn là:

var items = [4,5,4,6,3,4,5,2,23,1,4,4,4]
var uniqueItems = Array.from(new Set(items))

Trả về

[4, 5, 6, 3, 2, 23, 1]

Như le_m đã đề xuất, điều này cũng có thể được rút ngắn bằng cách sử dụng toán tử trải rộng , như

var uniqueItems = [...new Set(items)]

11
Lưu ý rằng mảng bên trong sẽ không hoạt độngArray.from(new Set([[1,2],[1,2],[1,2,3]]))
Alexander Goncharov

3
Hiệu suất của giải pháp này so với myArray.filter((v, i, a) => a.indexOf(v) === i);?
Simoyw

43
Xin lưu ý rằng nếu bạn sử dụng Setvà thêm các đối tượng thay vì các giá trị nguyên thủy, nó sẽ chứa các tham chiếu duy nhất đến các đối tượng. Vì vậy, các bộ strong let s = new Set([{Foo:"Bar"}, {Foo:"Bar"}]);sẽ trở lại này: Set { { Foo: 'Bar' }, { Foo: 'Bar' } }đó là một Setvới tham chiếu đối tượng duy nhất để đối tượng có chứa các giá trị như nhau. Nếu bạn viết let o = {Foo:"Bar"};và sau đó tạo một bộ có hai tham chiếu như vậy: let s2 = new Set([o,o]);thì s2 sẽ làSet { { Foo: 'Bar' } }
mortb

2
Cả hai tùy chọn này đều có vấn đề trên IE10 ngay cả khi có lớp polyfill.io
Thymine

2
trường hợp thử nghiệm mới jsperf.com/array-filter-unique-vs-new-set/1 có vẻ giống như new Setchiếc cúp của bạn
shuk

167

Tôi nhận ra câu hỏi này đã có hơn 30 câu trả lời rồi. Nhưng tôi đã đọc qua tất cả các câu trả lời hiện có trước và thực hiện nghiên cứu của riêng mình.

Tôi chia tất cả các câu trả lời cho 4 giải pháp có thể:

  1. Sử dụng tính năng ES6 mới: [...new Set( [1, 1, 2] )];
  2. Sử dụng đối tượng { }để ngăn chặn trùng lặp
  3. Sử dụng mảng trợ giúp [ ]
  4. Sử dụng filter + indexOf

Đây là mã mẫu được tìm thấy trong câu trả lời:

Sử dụng tính năng ES6 mới: [...new Set( [1, 1, 2] )];

function uniqueArray0(array) {
  var result = Array.from(new Set(array));
  return result    
}

Sử dụng đối tượng { }để ngăn chặn trùng lặp

function uniqueArray1( ar ) {
  var j = {};

  ar.forEach( function(v) {
    j[v+ '::' + typeof v] = v;
  });

  return Object.keys(j).map(function(v){
    return j[v];
  });
} 

Sử dụng mảng trợ giúp [ ]

function uniqueArray2(arr) {
    var a = [];
    for (var i=0, l=arr.length; i<l; i++)
        if (a.indexOf(arr[i]) === -1 && arr[i] !== '')
            a.push(arr[i]);
    return a;
}

Sử dụng filter + indexOf

function uniqueArray3(a) {
  function onlyUnique(value, index, self) { 
      return self.indexOf(value) === index;
  }

  // usage
  var unique = a.filter( onlyUnique ); // returns ['a', 1, 2, '1']

  return unique;
}

Và tôi tự hỏi cái nào nhanh hơn. Tôi đã tạo mẫu Google Sheet để kiểm tra các chức năng. Lưu ý: ECMA 6 không có sẵn trong Google Sheets, vì vậy tôi không thể kiểm tra nó.

Đây là kết quả của các bài kiểm tra: nhập mô tả hình ảnh ở đây

Tôi dự kiến ​​sẽ thấy mã sử dụng đối tượng { }sẽ giành chiến thắng vì nó sử dụng hàm băm. Vì vậy, tôi rất vui vì các thử nghiệm cho thấy kết quả tốt nhất cho thuật toán này trong Chrome và IE. Cảm ơn @rab cho .


Tùy chọn "bộ lọc + indexOf" cực kỳ chậm trên các mảng trên 100.000 mục. Tôi đã phải sử dụng phương pháp "bản đồ đối tượng" tuy nhiên nó phá vỡ sự sắp xếp ban đầu.
do

Là số cao hơn chậm hơn, không nhanh hơn?
fletchsod

3
@ fletchsod, số là thời gian tính bằng ms để chạy mã.
Max Makhrov

1
Những con số là wong! Tập lệnh ứng dụng Google chạy trên máy chủ của họ, không phải trên trình duyệt của khách hàng - và do đó hiệu suất trên Chrome / IE (hoặc hầu như bất kỳ trình duyệt nào) sẽ giống nhau!
Jay Dadhania

1
Mặc dù có thể chạy JS phía máy khách từ Google Script (thông qua google.script.host hoặc google.script.run - không chắc chắn về điều đó), câu trả lời nêu rõ rằng "ECMA 6 không có sẵn trong Google Sheets" - do đó chúng ta có thể giả định một cách an toàn rằng người đăng đã sử dụng Google Script phía máy chủ cho việc này - và do đó câu trả lời cho thấy hiệu suất của Máy chủ Google, không phải của trình duyệt !!
Jay Dadhania

134

Bạn cũng có thể sử dụng underscore.js .

console.log(_.uniq([1, 2, 1, 3, 1, 4]));
<script src="http://underscorejs.org/underscore-min.js"></script>

cái nào sẽ trở lại:

[1, 2, 3, 4]

22
Hãy làm điều này folks. Đừng cắm cái gì đó vào nguyên mẫu Array. Xin vui lòng.
Jacob Dalton

5
@JacobDalton - Đây không phải là mở rộng nguyên mẫu Array. Nó được đặt tên trong đối tượng _.
siêu sáng

25
@superluminary Tôi biết đó là lý do tại sao tôi nói xin vui lòng làm điều này. Giải pháp được chấp nhận đề nghị sửa đổi nguyên mẫu Array. KHÔNG làm điều đó.
Jacob Dalton

21
@JacobDalton Xin đừng làm điều này. Không cần thêm thư viện bổ sung chỉ cho một công việc nhỏ có thể thực hiện vớiarray = [...new Set(array)]

3
Điều này đã đánh bại mục đích sử dụng các tính năng ES và thêm nhiều thư viện chỉ làm phình to trang web nhiều hơn.
fletchsod

68

Một lớp lót, JavaScript thuần

Với cú pháp ES6

list = list.filter((x, i, a) => a.indexOf(x) == i)

x --> item in array
i --> index of item
a --> array reference, (in this case "list")

nhập mô tả hình ảnh ở đây

Với cú pháp ES5

list = list.filter(function (x, i, a) { 
    return a.indexOf(x) == i; 
});

Khả năng tương thích trình duyệt : IE9 +


14
@Spets Nó có chi phí bậc hai, vì vậy nó không phải là câu trả lời hay nhất
Oriol

@Spets giống như tất cả các bản sao / phân biệt ... vâng, hãy thông minh, sử dụng cơ số để sắp xếp trước, chậm hơn 0 (n2) trong tìm kiếm nhanh trong hầu hết các trường hợp.
doker

cảm ơn các bạn! Tôi đã không nhận ra nó khi lần đầu tiên tôi nhìn vào mã nhưng bây giờ nó rõ ràng hơn một chút.
Spets

51

Tôi đã tìm thấy một phương thức hay sử dụng jQuery

arr = $.grep(arr, function(v, k){
    return $.inArray(v ,arr) === k;
});

Lưu ý: Mã này được lấy từ bài đấm vịt của Paul Ailen - Tôi quên cung cấp tín dụng: P


8
Một giải pháp ngắn gọn, nhưng gọi inArray kém hiệu quả hơn so với gọi hasOwnProperty.
Smith

Đây cũng là O (N ^ 2), phải không? Trong khi từ điển hoặc phương pháp hasOwnProperty có thể sẽ là O (N * logN).
bay tốc độ

Điều này làm việc cho tôi, tất cả các giải pháp khác không được hỗ trợ là Internet Explorer.
kerl

51

Giải pháp ngắn nhất với ES6: [...new Set( [1, 1, 2] )];

Hoặc nếu bạn muốn sửa đổi nguyên mẫu Array (như trong câu hỏi ban đầu):

Array.prototype.getUnique = function() {
    return [...new Set( [this] )];
};

EcmaScript 6 chỉ được triển khai một phần trong các trình duyệt hiện đại vào lúc này (tháng 8 năm 2015), nhưng Babel đã trở nên rất phổ biến để dịch mã ES6 (và thậm chí ES7) trở lại ES5. Bằng cách đó bạn có thể viết mã ES6 ngay hôm nay!

Nếu bạn đang tự hỏi những gì ...có nghĩa là, nó được gọi là toán tử lây lan . Từ MDN : «Toán tử trải rộng cho phép mở rộng một biểu thức ở những nơi có nhiều đối số (cho các lệnh gọi hàm) hoặc nhiều phần tử (đối với các mảng bằng chữ) được mong đợi». Vì Tập hợp là một lần lặp (và chỉ có thể có các giá trị duy nhất), nên toán tử trải sẽ mở rộng Tập để điền vào mảng.

Tài nguyên học ES6:


3
bạn có thể làm nó thậm chí còn ngắn hơn, với a = [...Set(a)], nhưng, dù sao, đây chỉ là Firefox.
c69

@ c69, phải, sẽ không được ngắn hơn thế. Người dùng SpiderMonkey cũng sẽ đánh giá cao.
Torsten Becker

Làm việc với Babel. Bạn chỉ cần bao gồm Array.from shim cũng:require ( "core-js/fn/array/from" );
Vladislav Rastrusny

nên là Array.prototype.getUnique = function () {return [... new Set ([this])]; }; hoặc, Array.prototype.getUnique = function () {return [... new Set (this)]; }; Tôi đã áp dụng nó trên sau- $ ('body'). Html (). ToLowerCase (). Match (/ ([a-zA-Z0-9 ._ + -] + @ [a-zA-Z0-9. _-] + \. [A-zA-Z0-9 ._-] +) / gi) .getUnique (); Cái đầu tiên trả về cho tôi một giá trị duy nhất trong đó cái thứ hai trả lại tất cả các bản sao loại bỏ.
ashique

[...Set(['a', 1, 'a', 2, '1'])]sẽ ném TypeError, vì vậy vẫn nên giữ lại new:[...new Set(['a', 1, 'a', 2, '1'])]
Adam Katz

36

Giải pháp đơn giản nhất:

var arr = [1, 3, 4, 1, 2, 1, 3, 3, 4, 1];
console.log([...new Set(arr)]);

Hoặc là:

var arr = [1, 3, 4, 1, 2, 1, 3, 3, 4, 1];
console.log(Array.from(new Set(arr)));


4
Lưu ý rằng điều này không được hỗ trợ trên IE10 trở xuống. Lưu ý rằng IE11 + và Safari cung cấp hỗ trợ hạn chế (thậm chí không chắc chắn rằng ví dụ trên hoạt động) developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
trộm

32

Cách đơn giản nhất và nhanh nhất (trong Chrome) để thực hiện việc này:

Array.prototype.unique = function() {
    var a = [];
    for (var i=0, l=this.length; i<l; i++)
        if (a.indexOf(this[i]) === -1)
            a.push(this[i]);
    return a;
}

Đơn giản chỉ cần đi qua từng mục trong mảng, kiểm tra xem mục đó đã có trong danh sách chưa và nếu không, hãy đẩy đến mảng được trả về.

Theo jsPerf, chức năng này là nhanh nhất trong số những chức năng tôi có thể tìm thấy ở bất cứ đâu - mặc dù vậy, hãy tự mình thêm vào.

Phiên bản không phải nguyên mẫu:

function uniques(arr) {
    var a = [];
    for (var i=0, l=arr.length; i<l; i++)
        if (a.indexOf(arr[i]) === -1 && arr[i] !== '')
            a.push(arr[i]);
    return a;
}

Sắp xếp

Khi cũng cần sắp xếp mảng, sau đây là nhanh nhất:

Array.prototype.sortUnique = function() {
    this.sort();
    var last_i;
    for (var i=0;i<this.length;i++)
        if ((last_i = this.lastIndexOf(this[i])) !== i)
            this.splice(i+1, last_i-i);
    return this;
}

hoặc không nguyên mẫu:

function sortUnique(arr) {
    arr.sort();
    var last_i;
    for (var i=0;i<arr.length;i++)
        if ((last_i = arr.lastIndexOf(arr[i])) !== i)
            arr.splice(i+1, last_i-i);
    return arr;
}

Điều này cũng nhanh hơn phương pháp trên trong hầu hết các trình duyệt không phải là chrome.


Trên Linux, Chrome 55.0.2883 thích Array.unique () của bạn và Arrayclone2.sortFilter () chậm nhất (chậm hơn 78%). Tuy nhiên, Firefox 51.0.0 (có rất nhiều addon) có tốc độ nhanh nhất (vẫn chậm hơn so với Ops / giây so với bất kỳ kết quả Chrome nào khác) với jQuery $ .grep (mảng, jqFilter) chậm nhất (chậm hơn 46%). Mảng của bạn.uniq () chậm hơn 30%. Tôi đã chạy từng bài kiểm tra hai lần và nhận được kết quả nhất quán. Rafael's Array.getUnique () có vị trí thứ hai trong cả hai trình duyệt.
Adam Katz

jsPerf hiện đang có lỗi , vì vậy bản chỉnh sửa của tôi cho bài kiểm tra này đã không cam kết tất cả mọi thứ, nhưng nó đã dẫn đến việc thêm hai bài kiểm tra: Cocco's toUnique () đánh bại danh sách ES6 của Vamsi.filter () trên cả hai trình duyệt, đánh bại sortFilter của swilliams () cho # 1 trên FF (sortFilter chậm hơn 16%) và đánh bại thử nghiệm được sắp xếp của bạn (chậm hơn 2%) cho # 3 trên Chrome.
Adam Katz

Ah, tôi đã không nhận ra rằng những bài kiểm tra đó là nhỏ và không thực sự quan trọng. Một nhận xét cho câu trả lời được chấp nhận mô tả vấn đề đó và đưa ra sự điều chỉnh trong bản sửa đổi cho bài kiểm tra, trong đó mã của Rafael dễ dàng là mã nhanh nhất và mã Array.unique của Joetje50 chậm hơn 98%. Tôi cũng đã thực hiện một sửa đổi khác như ghi chú trong bình luận này .
Adam Katz

1
Chà, thực ra thuật toán bạn thực hiện trong uniquehàm có độ phức tạp O (n ^ 2) trong khi thuật toán trong getUniquelà O (n). Cái đầu tiên có thể nhanh hơn trên các tập dữ liệu nhỏ, nhưng làm thế nào bạn có thể tranh luận với toán học :) Bạn có thể chắc chắn rằng cái sau nhanh hơn nếu bạn chạy nó trên một mảng, giả sử, 1e5 mục duy nhất
Mikhail Dudin

31

CHỈ CÓ HIỆU SUẤT! mã này có thể nhanh hơn 10 lần so với tất cả các mã ở đây * hoạt động trên tất cả các trình duyệt và cũng có tác động bộ nhớ thấp nhất .... và hơn thế nữa

nếu bạn không cần sử dụng lại mảng cũ, btw thực hiện các thao tác cần thiết khác trước khi bạn chuyển đổi nó thành duy nhất ở đây có lẽ là cách nhanh nhất để làm điều này, cũng rất ngắn.

var array=[1,2,3,4,5,6,7,8,9,0,1,2,1];

sau đó bạn có thể thử điều này

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 1];

function toUnique(a, b, c) { //array,placeholder,placeholder
  b = a.length;
  while (c = --b)
    while (c--) a[b] !== a[c] || a.splice(c, 1);
  return a // not needed ;)
}
console.log(toUnique(array));
//[3, 4, 5, 6, 7, 8, 9, 0, 2, 1]

Tôi đã đưa ra chức năng này khi đọc bài viết này ...

http://www.shamocation.net/2009/09/fast-alerskym-to-find-unique-items-in-javascript-array/

Tôi không thích vòng lặp for. nó có nhiều tham số. tôi thích vòng lặp while--. trong khi đó là vòng lặp nhanh nhất trong tất cả các trình duyệt ngoại trừ trình duyệt mà tất cả chúng ta đều rất thích ... chrome.

dù sao tôi đã viết hàm đầu tiên sử dụng while. Và vâng, nó nhanh hơn một chút so với hàm tìm thấy trong bài viết. Nhưng không đủ.unique2()

Bước tiếp theo sử dụng js hiện đại. Object.keys tôi đã thay thế vòng lặp khác bằng Object.keys của js1.7 ... nhanh hơn và ngắn hơn một chút (trong chrome 2x nhanh hơn);). Không đủ!. unique3().

tại thời điểm này tôi đã suy nghĩ về những gì tôi thực sự cần trong chức năng độc đáo của tôi. Tôi không cần mảng cũ, tôi muốn một chức năng nhanh. Vì vậy, tôi đã sử dụng 2 trong khi vòng lặp + mối nối.unique4()

Vô dụng để nói rằng tôi đã rất ấn tượng.

trình duyệt Chrome: 150.000 hoạt động thông thường mỗi giây đã nhảy vọt lên 1.800.000 hoạt động mỗi giây.

tức là: 80.000 op / s so với 3.500.000 op / s

ios: 18.000 op / s so với 170.000 op / s

safari: 80.000 op / s so với 6.000.000 op / s

Bằng chứng http://jsperf.com/wgu hoặc tốt hơn là sử dụng console.time ... microtime ... bất cứ điều gì

unique5() chỉ để cho bạn thấy những gì xảy ra nếu bạn muốn giữ mảng cũ.

Đừng sử dụng Array.prototypenếu bạn không biết bạn đang làm gì. tôi chỉ làm rất nhiều bản sao và quá khứ. Sử dụng Object.defineProperty(Array.prototype,...,writable:false,enumerable:false})nếu bạn muốn tạo một nguyên mẫu gốc. Ví dụ: https://stackoverflow.com/a/20463021/2450730

Bản giới thiệu http://jsfiddle.net/46S7g/

LƯU Ý: mảng cũ của bạn bị hủy / becomestheunique sau thao tác này.

nếu bạn không thể đọc mã ở trên, hãy đọc một cuốn sách javascript hoặc đây là một số giải thích về mã ngắn hơn. https://stackoverflow.com/a/21353032/2450730

một số đang sử dụng indexOf ... đừng ... http://jsperf.com/dgfgghfghfghghgfhgfhfghfhgfh

cho mảng trống

!array.length||toUnique(array); 

4
đã thử nghiệm trên node.js, với một mảng Url (chuỗi) 100 nghìn. Kết quả chậm hơn 2 lần so với underscore.js _.uniq ... mặc dù một jsperf riêng biệt đồng ý với bạn ( jsperf.com/uniq-performance/5 ), tôi thất vọng :(
xShirase

3
bạn không kiểm tra chính xác trong jsperf ... trong ví dụ của bạn, bạn xác định hàm mọi lúc ... nhưng các hàm underscore.js đã được xác định .. điều này sẽ phạt chức năng của tôi. cũng kiểm tra 3 & 4. một điều khác tôi nên nói là nếu bạn sử dụng các biến hỗn hợp (chuỗi & số), bạn nên thay thế [b]! == a [c] bằng [b]! = a [c]
cocco

2
Tôi không chắc liệu jsPerf có đúng hay không, nhưng dường như phương án Giảm (điều đó đối với tôi dễ hiểu hơn) nhanh hơn 94% so với giải pháp của bạn. jsperf.com/reduce-for-distinc Chỉnh sửa: Bạn chậm hơn trên chrome, tương tự trên Edge và nhanh hơn trên Firefox.
Victor Ivens

3
Chỉ có thể sử dụng với các mảng nhỏ / tỷ lệ trùng lặp thấp . Mỗi lần tìm thấy vật thể không phải là duy nhất, động cơ sẽ buộc phải dịch chuyển tất cả các yếu tố còn lại sang trái, bằng cách: 1) lặp qua chúng 2) đọc chúng 3) ghi chúng vào vị trí mới. Và sau đó, nó cũng tạo ra mảng mới với một phần tử được ghép nối đó và trả về kết quả là ... Thuật toán nhanh chóng xuống cấp với các mảng lớn hơn / trùng lặp thường xuyên hơn. Đối với các mảng có kích thước 1000-> 10000-> 50000 và ~ 40% trùng lặp, thời gian trung bình sẽ là khoảng 2-> 775-> 19113 ms. (kích thước thay đổi là 1-> x10-> x5, thời gian là 1-> x10-> x25) trong Chrome.
ankhzet

1
Cảm ơn. Cách tiếp cận tốt đẹp. Nhưng những gì về thứ tự chính xác thứ tự?
do

28

Nhiều câu trả lời ở đây có thể không hữu ích cho người mới bắt đầu. Nếu việc khử một mảng là khó khăn, liệu họ có thực sự biết về chuỗi nguyên mẫu hay thậm chí là jQuery không?

Trong các trình duyệt hiện đại, một giải pháp đơn giản và sạch sẽ là lưu trữ dữ liệu trong Tập hợp , được thiết kế để trở thành một danh sách các giá trị duy nhất.

const cars = ['Volvo', 'Jeep', 'Volvo', 'Lincoln', 'Lincoln', 'Ford'];
const uniqueCars = Array.from(new Set(cars));

Điều Array.fromnày rất hữu ích để chuyển đổi Set back thành Array để bạn có thể truy cập dễ dàng vào tất cả các phương thức (tính năng) tuyệt vời mà mảng có. Cũng có những cách khác để làm điều tương tự. Nhưng bạn có thể không cần Array.fromchút nào, vì Bộ có nhiều tính năng hữu ích như forEach .

Nếu bạn cần hỗ trợ Internet Explorer cũ và do đó không thể sử dụng Set, thì một kỹ thuật đơn giản là sao chép các mục sang một mảng mới trong khi kiểm tra trước nếu chúng đã ở trong mảng mới.

// Create a list of cars, with duplicates.
var cars = ['Volvo', 'Jeep', 'Volvo', 'Lincoln', 'Lincoln', 'Ford'];
// Create a list of unique cars, to put a car in if we haven't already.
var uniqueCars = [];

// Go through each car, one at a time.
cars.forEach(function (car) {
    // The code within the following block runs only if the
    // current car does NOT exist in the uniqueCars list
    // - a.k.a. prevent duplicates
    if (uniqueCars.indexOf(car) === -1) {
        // Since we now know we haven't seen this car before,
        // copy it to the end of the uniqueCars list.
        uniqueCars.push(car);
    }
});

Để làm cho nó có thể tái sử dụng ngay lập tức, hãy đặt nó vào một chức năng.

function deduplicate(data) {
    if (data.length > 0) {
        var result = [];

        data.forEach(function (elem) {
            if (result.indexOf(elem) === -1) {
                result.push(elem);
            }
        });

        return result;
    }
}

Vì vậy, để loại bỏ các bản sao, bây giờ chúng ta sẽ làm điều này.

var uniqueCars = deduplicate(cars);

Các deduplicate(cars)phần trở thành điều chúng tôi đặt tên kết quả khi hoàn thành chức năng.

Chỉ cần vượt qua nó tên của bất kỳ mảng nào bạn thích.


Nhân tiện, tôi đã sử dụng một mảng đầy các chuỗi để cho thấy rằng kỹ thuật của tôi là linh hoạt. Nó sẽ hoạt động đúng cho số.
Seth Holladay

20
["Defects", "Total", "Days", "City", "Defects"].reduce(function(prev, cur) {
  return (prev.indexOf(cur) < 0) ? prev.concat([cur]) : prev;
 }, []);

[0,1,2,0,3,2,1,5].reduce(function(prev, cur) {
  return (prev.indexOf(cur) < 0) ? prev.concat([cur]) : prev;
 }, []);

Bạn có thể giải thích lý do tại sao bạn không chỉ đơn giản pushlà phần tử trên mảng thay vì sử dụng concat? Tôi đã thử sử dụng đẩy và nó đã thất bại. Tôi đang tìm kiếm một lời giải thích.
makenova 14/07/2015

1
Lý do là trong giá trị trả lại. concat trả về mảng đã sửa đổi (chính xác những gì cần được trả về bên trong hàm less), trong khi đẩy trả về một chỉ mục mà tại đó bạn có thể truy cập giá trị đẩy. Câu trả lời đó có đáp ứng được câu hỏi của bạn không?
sergeyz 14/07/2015

Rõ ràng, giải pháp này khá nhanh trong chrome (và nút). jsperf.com/reduce-for-distinc Đó là phiên bản ES6: [0,1,2,0,3,2,1,5].reduce((prev, cur) => ~prev.indexOf(cur) ? prev : prev.concat([cur]), []);
Victor Ivens

lưu ý phụ: việc triển khai này không NaNthân thiện
Alireza

19

Chúng tôi có thể làm điều này bằng cách sử dụng các bộ ES6:

var duplicatedArray = [1, 2, 3, 4, 5, 1, 1, 1, 2, 3, 4];
var uniqueArray = Array.from(new Set(duplicatedArray));

console.log(uniqueArray);

// Đầu ra sẽ là

uniqueArray = [1,2,3,4,5];

17

Nguyên mẫu getUniquenày không hoàn toàn chính xác, bởi vì nếu tôi có một Array như: ["1",1,2,3,4,1,"foo"]nó sẽ trả về ["1","2","3","4"]"1"là chuỗi và 1là một số nguyên; họ khác nhau.

Đây là một giải pháp chính xác:

Array.prototype.unique = function(a){
    return function(){ return this.filter(a) }
}(function(a,b,c){ return c.indexOf(a,b+1) < 0 });

sử dụng:

var foo;
foo = ["1",1,2,3,4,1,"foo"];
foo.unique();

Ở trên sẽ sản xuất ["1",2,3,4,1,"foo"].


1
Lưu ý rằng đó $foo = 'bar'là cách khai báo các biến của PHP. Nó sẽ hoạt động trong javascript, nhưng sẽ tạo ra một toàn cầu ngầm và thường không nên thực hiện.
Camilo Martin

@CamiloMartin xin lỗi nhưng bạn đã nhầm, $ foo là toàn cầu vì ví dụ này không được đóng và anh ấy thiếu từ khóa var. Không có gì để làm với đô la jsfiddle.net/robaldred/L2MRb
Rob

8
@Rob đó chính xác là những gì tôi đang nói, mọi người PHP sẽ nghĩ $foolà cách khai báo các biến trong javascript trong khi thực tế var foolà như vậy.
Camilo Martin

13

Nếu không mở rộng Array.prototype (nó được cho là một cách thực hành tồi) hoặc sử dụng jquery / gạch dưới, bạn có thể chỉ cần filtermảng.

Bằng cách giữ sự xuất hiện cuối cùng:

    function arrayLastUnique(array) {
        return array.filter(function (a, b, c) {
            // keeps last occurrence
            return c.indexOf(a, b + 1) < 0;
        });
    },

hoặc lần đầu tiên xuất hiện:

    function arrayFirstUnique(array) {
        return array.filter(function (a, b, c) {
            // keeps first occurrence
            return c.indexOf(a) === b;
        });
    },

Chà, nó chỉ là javascript ECMAScript 5+, có nghĩa là chỉ IE9 +, nhưng thật tuyệt khi phát triển HTML / JS bản địa (Ứng dụng Windows Store, Firefox OS, Sencha, Phonegap, Titanium, ...).


2
Thực tế là js 1.6 không có nghĩa là bạn không thể sử dụng filter. Tại trang MDN, họ có một triển khai cho Internet Explorer, ý tôi là, các trình duyệt cũ hơn. Ngoài ra: JS 1.6 chỉ đề cập đến công cụ js của Firefox, nhưng điều đúng đắn để nói rằng đó là ECMAScript 5.
Camilo Martin

@CamiloMartin Tôi đã đổi 1.6 thành ECMAScript5. Cảm ơn.
Cœur

13

ma thuật

a.filter(e=>!(t[e]=e in t)) 

Hiệu suất O (n) ; chúng tôi giả sử mảng của bạn là trong at={}. Giải thích tại đây (+ Jeppe .)


14
Điều này trông rất tuyệt, đến nỗi không có lời giải thích chắc chắn, tôi đã ngã, bạn sẽ khai thác bitcoin khi tôi chạy nó
Ondřej elazko

3
điều tôi muốn nói là bạn nên mở rộng câu trả lời của mình bằng một số giải thích và nhận xét giải cấu trúc của nó. đừng hy vọng mọi người sẽ tìm thấy câu trả lời hữu ích như thế này. (mặc dù nó thực sự trông rất tuyệt nhưng có lẽ nó hoạt động)
Ondřej elazko

1
@Jeppe khi tôi chạy cải tiến của bạn thì tôi gặp hiệu ứng aha (trước khi tôi không biết rằng tôi có thể sử dụng intoán tử bên ngoài công trình khác ngoài forvòng lặp: P) - Cảm ơn bạn - Tôi đánh giá cao nó và sẽ cung cấp +2 cho các câu trả lời tốt khác của bạn .
Kamil Kiełczewski

2
không phải điều đó tạo ra một biến toàn cục tmà vẫn tồn tại sau khi lọc thì sao ??
philipp

1
Đây thực sự là một giải pháp khó khăn. Thật không may, bạn phải khai báo bên ngoài biểu thức trừ khi bạn muốn xác định một biến toàn cục và / hoặc dựa vào bộ đóng gói mô-đun của bạn để ẩn toàn cục. Trông có vẻ lạ mắt, nhưng hãy tự mình (và nhóm phát triển của bạn) ủng hộ và chỉ cần viết một hai lớp lót sau đó: let t = {}; a.filter (e =>! (t [e] = e in t))
ford04

13
[...new Set(duplicates)]

Đây là cách đơn giản nhất và được tham chiếu từ MDN Web Docs .

const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)]) // [2, 3, 4, 5, 6, 7, 32]

Mặc dù mã này có thể giải quyết câu hỏi, bao gồm giải thích về cách thức và lý do giải quyết vấn đề này thực sự sẽ giúp cải thiện chất lượng bài đăng của bạn và có thể dẫn đến nhiều lượt bình chọn hơn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai, không chỉ người hỏi bây giờ. Vui lòng chỉnh sửa câu trả lời của bạn để thêm giải thích và đưa ra dấu hiệu về những hạn chế và giả định được áp dụng.
id.ot

11

Nếu bạn đang sử dụng khung Prototype, không cần phải thực hiện các vòng lặp 'cho', bạn có thể sử dụng http://www.prototypejs.org/api/array/uniq như thế này:

var a = Array.uniq();  

Mà sẽ tạo ra một mảng trùng lặp không có bản sao. Tôi đã bắt gặp câu hỏi của bạn khi tìm kiếm một phương pháp để đếm các bản ghi mảng khác nhau để sau đó

uniq ()

Tôi đã sử dụng

kích thước()

và đó là kết quả đơn giản của tôi. ps Xin lỗi nếu tôi đánh giá sai một cái gì đó

chỉnh sửa: nếu bạn muốn thoát các bản ghi không xác định, bạn có thể muốn thêm

gọn nhẹ()

trước đây, như thế này:

var a = Array.compact().uniq();  

14
bởi vì tôi đã tìm thấy một câu trả lời tốt hơn, tôi nghĩ về các chủ đề dành cho tất cả mọi người không chỉ dành cho người đã hỏi
Decebal

11

Bây giờ bằng cách sử dụng các bộ, bạn có thể loại bỏ các bản sao và chuyển đổi chúng trở lại mảng.

var names = ["Mike","Matt","Nancy", "Matt","Adam","Jenny","Nancy","Carl"];

console.log([...new Set(names)])

Một giải pháp khác là sử dụng sort & filter

var names = ["Mike","Matt","Nancy", "Matt","Adam","Jenny","Nancy","Carl"];
var namesSorted = names.sort();
const result = namesSorted.filter((e, i) => namesSorted[i] != namesSorted[i+1]);
console.log(result);


10

Đó là bởi vì 0giá trị giả trong JavaScript.

this[i] sẽ bị làm sai nếu giá trị của mảng bằng 0 hoặc bất kỳ giá trị giả nào khác.


Ahhhh, ok tôi thấy bây giờ ... nhưng sẽ có một sửa chữa dễ dàng để làm cho nó hoạt động?
Mottie

10
Array.prototype.getUnique = function() {
    var o = {}, a = []
    for (var i = 0; i < this.length; i++) o[this[i]] = 1
    for (var e in o) a.push(e)
    return a
}

Tôi nghĩ rằng điều này sẽ không hoạt động nếu mảng chứa các đối tượng / mảng và tôi không chắc liệu nó có giữ được kiểu vô hướng hay không.
Camilo Martin

Vâng, mọi thứ đều được xâu chuỗi. Điều đó có thể được khắc phục bằng cách lưu trữ giá trị ban đầu othay vì chỉ một 1, mặc dù so sánh bằng vẫn sẽ theo chuỗi (mặc dù, trong số tất cả các đẳng thức Javascript có thể, có vẻ như không quá vô lý).
ephemient

Array.prototype chỉ có thể được mở rộng với các phương thức không thể đếm được .... Object.defineProperty (Array.prototype, "getUnique", {}) ... nhưng ý tưởng sử dụng một đối tượng trợ giúp là rất hay
bortunac

10

Tôi đã có một vấn đề hơi khác khi tôi cần xóa các đối tượng có thuộc tính id trùng lặp khỏi một mảng. cái này hiệu quả

let objArr = [{
  id: '123'
}, {
  id: '123'
}, {
  id: '456'
}];

objArr = objArr.reduce((acc, cur) => [
  ...acc.filter((obj) => obj.id !== cur.id), cur
], []);

console.log(objArr);


9

Câu trả lời đơn giản nhất là:

const array = [1, 1, 2, 2, 3, 5, 5, 2];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // [1, 2, 3, 5]

Đơn giản nhưng nó không hoạt động với một loạt các đối tượng.
Thanwa Ch.

7

Tôi không chắc tại sao Gabriel Silveira lại viết chức năng theo cách đó nhưng một hình thức đơn giản hơn cũng phù hợp với tôi và không có sự thu nhỏ là:

Array.prototype.unique = function() {
  return this.filter(function(value, index, array) {
    return array.indexOf(value, index + 1) < 0;
  });
};

hoặc trong CoffeeScript:

Array.prototype.unique = ->
  this.filter( (value, index, array) ->
    array.indexOf(value, index + 1) < 0
  )

7

Nếu bạn ổn với các phụ thuộc bổ sung hoặc bạn đã có một trong các thư viện trong cơ sở mã của mình, bạn có thể xóa các bản sao khỏi một mảng tại chỗ bằng LoDash (hoặc Underscore).

Sử dụng

Nếu bạn chưa có nó trong cơ sở mã của mình, hãy cài đặt nó bằng npm:

npm install lodash

Sau đó sử dụng nó như sau:

import _ from 'lodash';
let idArray = _.uniq ([
    1,
    2,
    3,
    3,
    3
]);
console.dir(idArray);

Ngoài:

[ 1, 2, 3 ]

7

Điều này đã được trả lời rất nhiều, nhưng nó không giải quyết được nhu cầu đặc biệt của tôi.

Nhiều câu trả lời như thế này:

a.filter((item, pos, self) => self.indexOf(item) === pos);

Nhưng điều này không làm việc cho các mảng của các đối tượng phức tạp.

Nói rằng chúng ta có một mảng như thế này:

const a = [
 { age: 4, name: 'fluffy' },
 { age: 5, name: 'spot' },
 { age: 2, name: 'fluffy' },
 { age: 3, name: 'toby' },
];

Nếu chúng ta muốn các đối tượng có tên duy nhất, chúng ta nên sử dụng array.prototype.findIndexthay vì array.prototype.indexOf:

a.filter((item, pos, self) => self.findIndex(v => v.name === item.name) === pos);

Giải pháp tuyệt vời, hãy cẩn thận rằng một mảng mới sẽ trả về từ một hàm. (nó không tự sửa đổi)
Thanwa Ch.

6

Từ Shamasis Bhattacharya viết blog 's (O (2n) thời gian phức tạp):

Array.prototype.unique = function() {
    var o = {}, i, l = this.length, r = [];
    for(i=0; i<l;i+=1) o[this[i]] = this[i];
    for(i in o) r.push(o[i]);
    return r;
};

Từ blog của Paul Irish : cải tiến trên JQuery .unique():

(function($){

    var _old = $.unique;

    $.unique = function(arr){

        // do the default behavior only if we got an array of elements
        if (!!arr[0].nodeType){
            return _old.apply(this,arguments);
        } else {
            // reduce the array to contain no dupes via grep/inArray
            return $.grep(arr,function(v,k){
                return $.inArray(v,arr) === k;
            });
        }
    };
})(jQuery);

// in use..
var arr = ['first',7,true,2,7,true,'last','last'];
$.unique(arr); // ["first", 7, true, 2, "last"]

var arr = [1,2,3,4,5,4,3,2,1];
$.unique(arr); // [1, 2, 3, 4, 5]

6

Tìm giá trị mảng duy nhất trong phương thức đơn giản

function arrUnique(a){
  var t = [];
  for(var x = 0; x < a.length; x++){
    if(t.indexOf(a[x]) == -1)t.push(a[x]);
  }
  return t;
}
arrUnique([1,4,2,7,1,5,9,2,4,7,2]) // [1, 4, 2, 7, 5, 9]

6

Có vẻ như chúng ta đã mất câu trả lời của Rafael , được coi là câu trả lời được chấp nhận trong một vài năm. Đây là (ít nhất là trong năm 2017) là giải pháp hiệu quả nhất nếu bạn không có mảng kiểu hỗn hợp :

Array.prototype.getUnique = function(){
    var u = {}, a = [];
    for (var i = 0, l = this.length; i < l; ++i) {
        if (u.hasOwnProperty(this[i])) {
            continue;
        }
        a.push(this[i]);
        u[this[i]] = 1;
    }
return a;
}

Nếu bạn làm có một mảng hỗn hợp kiểu, bạn có thể serialize phím băm:

Array.prototype.getUnique = function() {
    var hash = {}, result = [], key; 
    for ( var i = 0, l = this.length; i < l; ++i ) {
        key = JSON.stringify(this[i]);
        if ( !hash.hasOwnProperty(key) ) {
            hash[key] = true;
            result.push(this[i]);
        }
    }
    return result;
}

5

Để giải quyết vấn đề theo cách khác, có thể không có sự trùng lặp trong khi bạn tải mảng của mình, cách đối tượng Set sẽ làm điều đó nhưng nó chưa khả dụng trong tất cả các trình duyệt. Nó tiết kiệm bộ nhớ và hiệu quả hơn nếu bạn cần xem nội dung của nó nhiều lần.

Array.prototype.add = function (elem) {
   if (this.indexOf(elem) == -1) {
      this.push(elem);
   }
}

Mẫu vật:

set = [];
[1,3,4,1,2,1,3,3,4,1].forEach(function(x) { set.add(x); });

Mang đến cho bạn set = [1,3,4,2]

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.