Как получить уникальные значения в массиве

168

Как я могу получить список уникальных значений в массиве? Всегда ли мне нужно использовать второй массив или в JavaScript есть что-то похожее на hashmap в java?

Я собираюсь использовать только JavaScript и jQuery . Никакие дополнительные библиотеки не могут быть использованы.

астронавт
источник
2
stackoverflow.com/questions/5381621/… - точно описывает, что вы хотите, я думаю?
SpaceBison
1
вы открыты для использования underscore.jsбиблиотеки?
Джейки
хэш-карта Java в основном такая же, как объект JavaScript. синтаксис: {"ключ": "значение", "ключ2": "значение2"}
Ян
API коллекции JavaScript / TypeScript ужасны по сравнению со Scala,list.toSet
Джордан Стюарт

Ответы:

120

Поскольку я рассказал об этом в комментариях к ответу @ Rocket, я также могу привести пример, в котором не используются библиотеки. Это требует двух новых функций прототипа, containsиunique

Array.prototype.contains = function(v) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] === v) return true;
  }
  return false;
};

Array.prototype.unique = function() {
  var arr = [];
  for (var i = 0; i < this.length; i++) {
    if (!arr.contains(this[i])) {
      arr.push(this[i]);
    }
  }
  return arr;
}

var duplicates = [1, 3, 4, 2, 1, 2, 3, 8];
var uniques = duplicates.unique(); // result = [1,3,4,2,8]

console.log(uniques);

Для большей надежности вы можете заменить containsна indexOfпрокладку MDN и проверить, indexOfравен ли каждый элемент -1: документация

jackwanders
источник
1
~a.indexOf(b) === (a.indexOf(b) == -1)
Орвеллофил
1
@Lexynux: это был тот самый ответ, который должен использовать только тот, кто понимает, что это значит, и, возможно, даже тогда. То , что он говорит, что написание if (~a.indexOf(b)) ...является идентичным для написания больше if (a.indexOf(b) == -1) ....
Орвеллофил
4
это имеет высокую сложность во время выполнения (наихудший случай: O (n ^ 2))
Рахул Арора
1
это действительно неэффективная реализация. проверка результирующего массива, чтобы увидеть, содержит ли он элемент, ужасна. лучшим подходом было бы либо использовать объект, который отслеживает количество, либо, если вы не хотите использовать вспомогательное хранилище, сначала отсортируйте его по O (n log n), а затем по линейной развертке и сравните элементы рядом друг с другом
Исаия Ли
1
Нам действительно нужна функция «содержит»?
Анимеш Кумар
206

Или для тех, кто ищет однострочник (простой и функциональный), совместимый с современными браузерами :

let a = ["1", "1", "2", "3", "3", "1"];
let unique = a.filter((item, i, ar) => ar.indexOf(item) === i);
console.log(unique);

Обновление 18-04-2017

Похоже, что Array.prototype.includes теперь широко поддерживается в последних версиях основных браузеров ( совместимость )

Обновление 29-07-2015:

В планах браузеров работа по поддержке стандартного метода Array.prototype.include, который, хотя и не дает прямого ответа на этот вопрос; часто связано.

Использование:

["1", "1", "2", "3", "3", "1"].includes("2");     // true

Pollyfill ( поддержка браузера , источник из Mozilla ):

// https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) {
  Object.defineProperty(Array.prototype, 'includes', {
    value: function(searchElement, fromIndex) {

      // 1. Let O be ? ToObject(this value).
      if (this == null) {
        throw new TypeError('"this" is null or not defined');
      }

      var o = Object(this);

      // 2. Let len be ? ToLength(? Get(O, "length")).
      var len = o.length >>> 0;

      // 3. If len is 0, return false.
      if (len === 0) {
        return false;
      }

      // 4. Let n be ? ToInteger(fromIndex).
      //    (If fromIndex is undefined, this step produces the value 0.)
      var n = fromIndex | 0;

      // 5. If n ≥ 0, then
      //  a. Let k be n.
      // 6. Else n < 0,
      //  a. Let k be len + n.
      //  b. If k < 0, let k be 0.
      var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

      // 7. Repeat, while k < len
      while (k < len) {
        // a. Let elementK be the result of ? Get(O, ! ToString(k)).
        // b. If SameValueZero(searchElement, elementK) is true, return true.
        // c. Increase k by 1.
        // NOTE: === provides the correct "SameValueZero" comparison needed here.
        if (o[k] === searchElement) {
          return true;
        }
        k++;
      }

      // 8. Return false
      return false;
    }
  });
}
Джош Мак
источник
Это почти копия вставки из kennebec, но по общему признанию передача массива в качестве параметра, а не использование замыкания, вероятно, улучшит производительность.
- Должен сказать, что я не соединил точки, просто просканировал один лайнер, выглядел как большой пост, поэтому пропустил, пошел и нашел альтернативный источник и переиздал для других, чтобы быстро найти. Тем не менее, ваше право; почти так же, как Кеннебек.
Джош Мак
Приятно - не понимал, что фильтр отправляется в массив в качестве параметра и не хочет работать с внешним объектом. Это именно то, что мне нужно - моя версия javascript (более старая версия xerces) некоторое время не будет иметь новых вкусностей.
Джерард ONeill
1
@GerardONeill да, в некоторых ситуациях это очень важно, например, если он функционально связан и вам нужен доступ к массиву, которому не была назначена переменная, например .map (...). Filter (...)
Джош Мак
@ Джош Мак, не могли бы вы как-нибудь использовать метод «include» в «уникальном» методе?
vkelman
144

Вот гораздо более чистое решение для ES6, которое, как я вижу, здесь не включено. Он использует Set и оператор распространения :...

var a = [1, 1, 2];

[... new Set(a)]

Который возвращается [1, 2]

Чарльз Клейтон
источник
1
Это так умно!
Джош Мак
1
Теперь, ЭТО является один лайнер!
Mac
11
В Typescript вы должны использовать, Array.from(... new Set(a))так как Set не может быть неявно преобразован в тип массива. Просто на голову!
Zachscs
4
@Zachscs, я получил ошибку компиляции, когда попробовал это. Вы имели в виду только Array.from(new Set(a))? Это похоже на работу.
adam0101
72

Один лайнер, чистый JavaScript

С синтаксисом 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")

введите описание изображения здесь

С синтаксисом ES5

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

Совместимость браузера : IE9 +

Vamsi
источник
4
не уверен, почему это было отклонено. Поначалу он может быть немного неясным и, возможно, классифицирован как «умный» и не прагматичный для чтения, но он декларативный, неразрушающий и сжатый, где отсутствует большинство других ответов.
Ларри
1
@ Ларри, за это проголосовали, потому что точно такой же ответ был предоставлен за годы до этого.
Алекс Окрушко
@AlexOkrushko достаточно честно - пропустил этот ответ из-за того, как он был отформатирован
Ларри
7
Все в одну строку, если поместить все в одну строку :-)
Гэри МакГилл,
Было бы неплохо ужесточить a.indexOf(x) === iпримечание равенства с тремя знаками равенства.
treejanitor
21

Используя EcmaScript 2016, вы можете просто сделать это следующим образом.

 var arr = ["a", "a", "b"];
 var uniqueArray = Array.from(new Set(arr)); // Unique Array ['a', 'b'];

Наборы всегда уникальны, и с помощью них Array.from()вы можете конвертировать набор в массив. Для справки взгляните на документацию.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects /Устанавливать

Адель Имран
источник
1
Это ответ, который вы должны использовать. indexOf()ответы ужасны, потому что они O (N ^ 2). Распространенные ответы в порядке, но не будут работать для больших массивов. Это лучший подход.
Тимммм
20

Теперь в ES6 мы можем использовать недавно введенную функцию ES6

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

ИЛИ с помощью Array распространяет синтаксис на итерируемые

let items = [1,1,1,1,3,4,5,2,23,1,4,4,4,2,2,2]; 
let uniqueItems = [...new Set(items)];

Это вернет уникальный результат.

[1, 3, 4, 5, 2, 23]
Rohit.007
источник
1
Самое чистое решение!
Димитрис Филиппу,
Это путь, когда в среде JS, которая поддерживает new Setподобное (например, современный Angular / TypeScript)
Дон Чидл
1
Стоит отметить, что вы также можете использовать синтаксис разброса массива на итерациях, таких как Set и Map: let items = [1,1,1,1,3,4,5,2,23,1,4,4,4,2,2,2]; let uniqueItems = [...new Set(items)];
jacobedawson
Я люблю эти ответы ES6!
Глен Томпсон
1
Это идентично ответу @Adeel Imran. Пожалуйста, не делайте ответы, которые точно такие же, как существующие ответы.
Тимммм
16

Если вы хотите оставить исходный массив без изменений,

вам нужен второй массив для хранения уникальных элементов первого

Большинство браузеров имеют Array.prototype.filter:

var unique= array1.filter(function(itm, i){
    return array1.indexOf(itm)== i; 
    // returns true for only the first instance of itm
});


//if you need a 'shim':
Array.prototype.filter= Array.prototype.filter || function(fun, scope){
    var T= this, A= [], i= 0, itm, L= T.length;
    if(typeof fun== 'function'){
        while(i<L){
            if(i in T){
                itm= T[i];
                if(fun.call(scope, itm, i, T)) A[A.length]= itm;
            }
            ++i;
        }
    }
    return A;
}
 Array.prototype.indexOf= Array.prototype.indexOf || function(what, i){
        if(!i || typeof i!= 'number') i= 0;
        var L= this.length;
        while(i<L){
            if(this[i]=== what) return i;
            ++i;
        }
        return -1;
    }
Kennebec
источник
14

В наши дни вы можете использовать тип данных Set для ES6, чтобы преобразовать ваш массив в уникальный набор. Затем, если вам нужно использовать методы массива, вы можете превратить его обратно в массив:

var arr = ["a", "a", "b"];
var uniqueSet = new Set(arr); // {"a", "b"}
var uniqueArr = Array.from(uniqueSet); // ["a", "b"]
//Then continue to use array methods:
uniqueArr.join(", "); // "a, b"
Fawntasia
источник
1
Аккуратно, было бы неплохо увидеть некоторые показатели производительности, я думаю, что конвертирование этих наборов, в частности, если они очень динамичны и велики, может привести к снижению производительности, единственный способ сказать, что это тестирование :)
Astronaut
2
Если вы используете транспортер или находитесь в среде, которая его поддерживает, вы можете сделать то же самое более кратко, чем:var uniqueArr = [...new Set(arr)]; // ["a", "b"]
Стенерсон
Эй, @Astronaut сделали несколько тестов на производительность, как ты сказал?
alexventuraio
8

Не родной в Javascript, но во многих библиотеках есть этот метод.

Underscore.js _.uniq(array)( ссылка ) работает довольно хорошо ( источник ).

Кальвин
источник
Спасибо за акцию! Эта функция принимает итератор и контекст вместе с массивом в качестве списка аргументов из v1.4.3.
Kunj
6

Используя jQuery, вот уникальная функция Array, которую я сделал:

Array.prototype.unique = function () {
    var arr = this;
    return $.grep(arr, function (v, i) {
        return $.inArray(v, arr) === i;
    });
}

console.log([1,2,3,1,2,3].unique()); // [1,2,3]
Ракета Хазмат
источник
5
если вы собираетесь использовать jQuery внутри прототипа основного объекта javascript, не лучше ли написать функцию jQuery, например $.uniqueArray(arr)? Встраивание ссылок на jQuery в Arrayпрототипе выглядит сомнительным
jackwanders
1
@jackwanders: Что такого сомнительного в этом? Если у вас есть jQuery на странице, давайте использовать его.
Ракетный Хазмат
Просто новая уникальная функция, которую вы написали, теперь зависит от jQuery; Вы не можете переместить его на новый сайт или в приложение, не убедившись, что там используется jQuery.
Jackwanders
2
это была моя точка зрения; если вы собираетесь использовать jQuery, то сделайте саму функцию частью jQuery. Если бы я собирался расширить прототип основного объекта, я бы придерживался основного javascript, просто чтобы сохранить вещи для повторного использования. Если кто-то еще смотрит на ваш код, очевидно, что $.uniqueArrayон зависит от jQuery; менее очевидно, что Array.prototype.uniqueэто тоже.
Jackwanders
1
@ jackwanders: я думаю. Я использую это в своем коде, так как я всегда использую jQuery, и мне просто нравится расширять prototypes. Но теперь я понимаю вашу точку зрения. Я все равно оставлю это здесь.
Ракетный Хазмат
6

Короткое и приятное решение с использованием второго массива;

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

    var distinct_axes2=[];

    for(var i=0;i<axes2.length;i++)
        {
        var str=axes2[i];
        if(distinct_axes2.indexOf(str)==-1)
            {
            distinct_axes2.push(str);
            }
        }
    console.log("distinct_axes2 : "+distinct_axes2); // distinct_axes2 : 1,4,5,2,3
Прадип Шенолкар
источник
Короткий? и сладкий? Вы смотрели на лучшие решения?
Алекс Окрушко
5

Быстрый, компактный, без вложенных циклов, работает с любым объектом, а не только со строками и числами, принимает предикат и всего 5 строк кода !!

function findUnique(arr, predicate) {
  var found = {};
  arr.forEach(d => {
    found[predicate(d)] = d;
  });
  return Object.keys(found).map(key => found[key]); 
}

Пример: чтобы найти уникальные предметы по типу:

var things = [
  { name: 'charm', type: 'quark'},
  { name: 'strange', type: 'quark'},
  { name: 'proton', type: 'boson'},
];

var result = findUnique(things, d => d.type);
//  [
//    { name: 'charm', type: 'quark'},
//    { name: 'proton', type: 'boson'}
//  ] 

Если вы хотите, чтобы он нашел первый уникальный элемент вместо последнего, добавьте туда проверку found.hasOwnPropery ().

user1618323
источник
4

Вам нужен только ванильный JS, чтобы найти уникальные с Array.some и Array.reduce. С синтаксисом ES2015 это всего 62 символа.

a.reduce((c, v) => b.some(w => w === v) ? c : c.concat(v)), b)

Array.some и Array.reduce поддерживаются в IE9 + и других браузерах. Просто измените функции жирной стрелки для поддержки обычных функций в браузерах, которые не поддерживают синтаксис ES2015.

var a = [1,2,3];
var b = [4,5,6];
// .reduce can return a subset or superset
var uniques = a.reduce(function(c, v){
    // .some stops on the first time the function returns true                
    return (b.some(function(w){ return w === v; }) ?  
      // if there's a match, return the array "c"
      c :     
      // if there's no match, then add to the end and return the entire array                                        
      c.concat(v)}),                                  
  // the second param in .reduce is the starting variable. This is will be "c" the first time it runs.
  b);                                                 

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects / Array / Уменьшить

puppybits
источник
4

Большинство из приведенных выше решений имеют высокую сложность во время выполнения.

Вот решение, которое использует reduceи может выполнить работу за O (n) время.

Array.prototype.unique = Array.prototype.unique || function() {
        var arr = [];
	this.reduce(function (hash, num) {
		if(typeof hash[num] === 'undefined') {
			hash[num] = 1; 
			arr.push(num);
		}
		return hash;
	}, {});
	return arr;
}
    
var myArr = [3,1,2,3,3,3];
console.log(myArr.unique()); //[3,1,2];

Примечание:

Это решение не зависит от снижения. Идея состоит в том, чтобы создать карту объектов и вставить уникальные в массив.

Рахул Арора
источник
1

ES6 способ:

const uniq = (arr) => (arr.filter((item, index, arry) => (arry.indexOf(item) === index)));
Эшвин Аггарвал
источник
1

ты можешь использовать,

let arr1 = [1,2,1,3];
let arr2 = [2,3,4,5,1,2,3];

let arr3 = [...new Set([...arr1,...arr2])];

это даст вам уникальные элементы,

**> но есть подвох,

для этого "1" и 1 и являются элементами diff, **

Второй вариант - использовать метод фильтра в массиве.

ниранджан гарпале
источник
1

Вы можете ввести массив с дубликатами, а метод ниже вернет массив с уникальными элементами.

function getUniqueArray(array){
    var uniqueArray = [];
    if (array.length > 0) {
       uniqueArray[0] = array[0];
    }
    for(var i = 0; i < array.length; i++){
        var isExist = false;
        for(var j = 0; j < uniqueArray.length; j++){
            if(array[i] == uniqueArray[j]){
                isExist = true;
                break;
            }
            else{
                isExist = false;
            }
        }
        if(isExist == false){
            uniqueArray[uniqueArray.length] = array[i];
        }
    }
    return uniqueArray;
}
Мухаммед Харун Икбал
источник
0

Единственная проблема с решениями, представленными до сих пор, - это эффективность. Если вас это беспокоит (и вам, вероятно, следует), вам следует избегать вложенных циклов: for * for, filter * indexOf, grep * inArray, они все повторяют массив несколько раз. Вы можете реализовать один цикл с такими решениями, как этот или этот

Хесус Каррера
источник
0
Array.prototype.unique = function () {
    var dictionary = {};
    var uniqueValues = [];
    for (var i = 0; i < this.length; i++) {
        if (dictionary[this[i]] == undefined){
            dictionary[this[i]] = i;
            uniqueValues.push(this[i]);
        }
    }
    return uniqueValues; 
}
Hexer338
источник
0

Я пробовал эту проблему в чистом JS. Я выполнил следующие шаги: 1. Сортировать указанный массив, 2. Перебрать отсортированный массив, 3. Проверить предыдущее значение и следующее значение с текущим значением.

// JS
var inpArr = [1, 5, 5, 4, 3, 3, 2, 2, 2,2, 100, 100, -1];

//sort the given array
inpArr.sort(function(a, b){
    return a-b;
});

var finalArr = [];
//loop through the inpArr
for(var i=0; i<inpArr.length; i++){
    //check previous and next value 
  if(inpArr[i-1]!=inpArr[i] && inpArr[i] != inpArr[i+1]){
        finalArr.push(inpArr[i]);
  }
}
console.log(finalArr);

демонстрация

Санкет Наяк
источник
0
function findUniques(arr){
  let uniques = []
  arr.forEach(n => {
    if(!uniques.includes(n)){
      uniques.push(n)
    }       
  })
  return uniques
}

let arr = ["3", "3", "4", "4", "4", "5", "7", "9", "b", "d", "e", "f", "h", "q", "r", "t", "t"]

findUniques(arr)
// ["3", "4", "5", "7", "9", "b", "d", "e", "f", "h", "q", "r", "t"]
smithWEBtek
источник
0

Имея в виду, что indexOfон вернет первое вхождение элемента, вы можете сделать что-то вроде этого:

Array.prototype.unique = function(){
        var self = this;
        return this.filter(function(elem, index){
            return self.indexOf(elem) === index;
        })
    }
nikksan
источник
0

Если вам не нужно сильно беспокоиться о старых браузерах, это именно то, для чего предназначены наборы.

Объект Set позволяет хранить уникальные значения любого типа, будь то примитивные значения или ссылки на объекты.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set

const set1 = new Set([1, 2, 3, 4, 5, 1]);
// returns Set(5) {1, 2, 3, 4, 5}
Пол Купер
источник
0

Еще одна мысль по этому вопросу. Вот что я сделал, чтобы добиться этого с меньшим количеством кода.

var distinctMap = {};
var testArray = ['John', 'John', 'Jason', 'Jason'];
for (var i = 0; i < testArray.length; i++) {
  var value = testArray[i];
  distinctMap[value] = '';
};
var unique_values = Object.keys(distinctMap);

console.log(unique_values);

Син-Вэй Лин
источник
0

function findUnique(arr) {
  var result = [];
  arr.forEach(function(d) {
    if (result.indexOf(d) === -1)
      result.push(d);
  });
  return result;
}

var unique = findUnique([1, 2, 3, 1, 2, 1, 4]); // [1,2,3,4]
console.log(unique);

user1739150
источник
0

Вот подход с настраиваемой equalsфункцией, которая может использоваться как для примитивов, так и для пользовательских объектов:

Array.prototype.pushUnique = function(element, equalsPredicate = (l, r) => l == r) {
    let res = !this.find(item => equalsPredicate(item, element))
    if(res){
        this.push(element)
    }
    return res
}

использование:

//with custom equals for objects
myArrayWithObjects.pushUnique(myObject, (left, right) => left.id == right.id)

//with default equals for primitives
myArrayWithPrimitives.pushUnique(somePrimitive)
вирус
источник
0

Мой ответ использует Array.filterи Array.indexOfметоды, чтобы получить уникальные значения

array.filter((value, index, self)=>self.indexOf(value)===index)

Я видел этот подход на веб-сайте, но их код отличается от того, что выглядит здесь. Я упростил код до одной строки и разместил его здесь, чтобы кто-то получил от него пользу

Примечание: мой подход похож на один лайнер, опубликованный Джошем. Я оставляю это здесь, поскольку имена переменных говорят сами за себя в моем коде.

Ананд Радж
источник
-1

Я просто думал, можем ли мы использовать линейный поиск для устранения дубликатов:

JavaScript:
function getUniqueRadios() {

var x=document.getElementById("QnA");
var ansArray = new Array();
var prev;


for (var i=0;i<x.length;i++)
  {
    // Check for unique radio button group
    if (x.elements[i].type == "radio")
    {
            // For the first element prev will be null, hence push it into array and set the prev var.
            if (prev == null)
            {
                prev = x.elements[i].name;
                ansArray.push(x.elements[i].name);
            } else {
                   // We will only push the next radio element if its not identical to previous.
                   if (prev != x.elements[i].name)
                   {
                       prev = x.elements[i].name;
                       ansArray.push(x.elements[i].name);
                   }
            }
    }

  }

   alert(ansArray);

}

HTML:

<body>

<form name="QnA" action="" method='post' ">

<input type="radio"  name="g1" value="ANSTYPE1"> good </input>
<input type="radio" name="g1" value="ANSTYPE2"> avg </input>

<input type="radio"  name="g2" value="ANSTYPE3"> Type1 </input>
<input type="radio" name="g2" value="ANSTYPE2"> Type2 </input>


<input type="submit" value='SUBMIT' onClick="javascript:getUniqueRadios()"></input>


</form>
</body>
Суреш
источник
-1

Вот одно линейное решение проблемы:

var seriesValues = [120, 120, 120, 120];
seriesValues = seriesValues.filter((value, index, seriesValues) => (seriesValues.slice(0, index)).indexOf(value) === -1);
console.log(seriesValues);

Скопируйте и вставьте это в консоль браузера и получите результаты, йо :-)

Маниш Кумар
источник
-2

У меня есть встроенная функция JQuery Unique .

uniqueValues= jQuery.unique( duplicateValues );

Для получения дополнительной информации вы можете обратиться к документации по jquery API.

http://api.jquery.com/jquery.unique/

Митул Махешвари
источник
8
Обратите внимание, что это работает только с массивами элементов DOM, а не со строками или числами. - цитата из документации.
mco