Array.push (), если не существует?


247

Как я могу вставить в массив, если ни одно из значений не существует? Вот мой массив:

[
    { name: "tom", text: "tasty" },
    { name: "tom", text: "tasty" },
    { name: "tom", text: "tasty" },
    { name: "tom", text: "tasty" },
    { name: "tom", text: "tasty" }
]

Если я попытаюсь снова протолкнуть массив с помощью name: "tom"или text: "tasty", я не хочу, чтобы что-то происходило ... но если ни одного из них нет, я хочу, чтобы это произошло.push()

Как я могу это сделать?


5
Используйте словарь (хэш / дерево) вместо массива.
Томас Эдинг

Все ли это доступно в JavaScript?
Рахул


3
использовать сет
Drax

1
Набор не работает с массивом объектов
rffaguiar

Ответы:


114

Вы можете расширить прототип Array с помощью специального метода:

// check if an element exists in array using a comparer function
// comparer : function(currentElement)
Array.prototype.inArray = function(comparer) { 
    for(var i=0; i < this.length; i++) { 
        if(comparer(this[i])) return true; 
    }
    return false; 
}; 

// adds an element to the array if it does not already exist using a comparer 
// function
Array.prototype.pushIfNotExist = function(element, comparer) { 
    if (!this.inArray(comparer)) {
        this.push(element);
    }
}; 

var array = [{ name: "tom", text: "tasty" }];
var element = { name: "tom", text: "tasty" };
array.pushIfNotExist(element, function(e) { 
    return e.name === element.name && e.text === element.text; 
});

5
Я думаю, что ваш хранитель (компаратор?) Должен принять два аргумента, это упростит случай, когда добавленное значение встроено, а не в переменной, к которой вы можете обращаться в своей функции. array.pushIfNotExist ({имя: "том", текст: "вкусно"}, функция (a, b) {return a.name === b.name && a.text === b.text;});
Винсент Роберт

26
Мне интересно, почему он не является родным для языка - забудьте о том, как он реализован - идея «добавлять только если уникален» настолько фундаментальна, что предполагается, что она существует.
JustSteve

9
Лучше расширить прототип Array с помощью метода IndexOf из JavaScript 1.6 вместо вашего inArray.
Евгений Глухоторенко

7
Array.findIndex()это встроенная функция JS, которая будет выполнять то же, что и ваш код.

4
Прямое расширение встроенных объектов - плохая практика.
Слава Фомин II

429

Для массива строк (но не для массива объектов) вы можете проверить, существует ли элемент, вызвав его, .indexOf()а если нет, то просто вставить элемент в массив:

var newItem = "NEW_ITEM_TO_ARRAY";
var array = ["OLD_ITEM_1", "OLD_ITEM_2"];

array.indexOf(newItem) === -1 ? array.push(newItem) : console.log("This item already exists");

console.log(array)


50
Не уверен, почему это не помечено как правильное. Он не использует никаких внешних элементов, не требует создания расширения и очень прост. Идеальный ответ на вопрос ops.
pimbrouwers

39
В первоначальном вопросе значения массива являются объектами, а не строками (и это решение не работает как есть, если значения являются объектами).
Саймон Привет

12
@EmilPedersen - не совсем. Попробуйте if (a.indexOf({ name: "tom", text: "tasty" })!=-1) a.push({ name: "tom", text: "tasty" })дважды. Это добавит «похожий» объект дважды.
обычная щука

18
Этот ответ должен быть удален, поскольку он объективно неверен, но все же привлек наибольшее количество голосов.
Nikola

2
Это не правильный ответ, почему принимается? Он работает только с массивами Js, а не с объектами внутри массивов.
digitai

100

Это довольно легко сделать с помощью Array.findIndexфункции, которая принимает функцию в качестве аргумента:

var a = [{name:"bull", text: "sour"},
    { name: "tom", text: "tasty" },
    { name: "tom", text: "tasty" }
]
var index = a.findIndex(x => x.name=="bob")
// here you can check specific property for an object whether it exist in your array or not

if (index === -1){
    a.push({your_object});
}
else console.log("object already exists")

8
Это ответ, ребята!
Джафед

2
наиболее актуально добавить элемент в массив, если его нет
акшай багад

1
Спасибо, искал это везде.
dt192

41

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

var cleanArray = $.unique(clutteredArray);

вы можете быть заинтересованы в makeArray тоже

В предыдущем примере лучше всего сказать, что перед нажатием проверьте, существует ли он. Оглядываясь назад, я также вижу, что вы можете объявить его как часть прототипа (я думаю, это также расширение класса), так что никаких значительных улучшений ниже.

Только я не уверен, что indexOf - более быстрый маршрут, чем inArray? наверное.

Array.prototype.pushUnique = function (item){
    if(this.indexOf(item) == -1) {
    //if(jQuery.inArray(item, this) == -1) {
        this.push(item);
        return true;
    }
    return false;
}

22
Из ссылки jQuery: Note that this only works on arrays of DOM elements, not strings or numbers.Кроме того, indexOf не работает в IE8 :(
dmathisen

Вы можете использовать lodash _.indexOf, который будет работать в IE8
LTroya

25

Именно по этим причинам используйте библиотеку js, например underscore.js . Использование: union: вычисляет объединение переданных массивов: список по порядку уникальных элементов, присутствующих в одном или нескольких массивах.

_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
=> [1, 2, 3, 101, 10]

8
Обратите внимание, что это возвращает новый массив и фактически не передает в существующий массив.
ilovett

4
ИМХО, действительно, нет необходимости вносить рамки для тестирования чего-то такого простого
DrewT

24

Как это?

var item = "Hello World";
var array = [];
if (array.indexOf(item) === -1) array.push(item);

С объектом

var item = {name: "tom", text: "tasty"}
var array = [{}]
if (!array.find(o => o.name === 'tom' && o.text === 'tasty'))
    array.push(item)

4
array.findэто плохая идея, потому что он ищет весь массив. Используйте findIndex, который ищет только до первого вхождения.

3
@ K48 в соответствии с этим: stackoverflow.com/a/33759573/5227365 «найти» останавливается после того, как он нашел предмет
Паскаль

19

Я знаю, что это очень старый вопрос, но если вы используете ES6, вы можете использовать очень маленькую версию:

[1,2,3].filter(f => f !== 3).concat([3])

Очень просто, сначала добавьте фильтр, который удаляет элемент - если он уже существует, а затем добавьте его через concat.

Вот более реалистичный пример:

const myArray = ['hello', 'world']
const newArrayItem

myArray.filter(f => f !== newArrayItem).concat([newArrayItem])

Если ваш массив содержит объекты, вы можете адаптировать функцию фильтра следующим образом:

someArray.filter(f => f.some(s => s.id === myId)).concat([{ id: myId }])

3
Довольно элегантное решение здесь. Спасибо!
Джонатан Пиказо

18

Нажмите динамически

var a = [
  {name:"bull", text: "sour"},
  {name: "tom", text: "tasty" },
  {name: "Jerry", text: "tasty" }
]

function addItem(item) {
  var index = a.findIndex(x => x.name == item.name)
  if (index === -1) {
    a.push(item);
  }else {
    console.log("object already exists")
  }
}

var item = {name:"bull", text: "sour"};
addItem(item);

Простым способом

var item = {name:"bull", text: "sour"};
a.findIndex(x => x.name == item.name) == -1 ? a.push(item) : console.log("object already exists")

Если массив содержит только примитивные типы / простой массив

var b = [1, 7, 8, 4, 3];
var newItem = 6;
b.indexOf(newItem) === -1 && b.push(newItem);

2
Здоровья вашим рукам. Простое и красивое решение @Gopala raja naika
Насимба

11

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

Наборы разрешают только уникальные записи, что автоматически решает вашу проблему.

Наборы могут быть объявлены так:

const baz = new Set(["Foo","Bar"])

Спасибо за указание на это @Michael. Хорошее решение для тех случаев, когда мы хотим сохранять отдельные данные с минимальными усилиями. Кстати, важно отметить, что производительность массива выше, так как для загрузки элемента требуется меньше ресурсов процессора.
Бен

Вопрос о том Array.push, так ли Set.addэто эквивалентно?
Брай

9

Простой код: если 'indexOf' возвращает '-1', это означает, что элемент не находится внутри массива, тогда условие '=== -1' возвращает true / false.

Оператор & & означает «и», поэтому, если первое условие истинно, мы помещаем его в массив.

array.indexOf(newItem) === -1 && array.push(newItem);

@ D.Lawrence Да, теперь намного лучше.
Эрик Валеро

Есть другие принятые ответы, которые предоставляют вопрос ОП, и они были опубликованы некоторое время назад. При публикации ответа смотрите: Как мне написать хороший ответ? Пожалуйста, убедитесь, что вы добавили либо новое решение, либо существенно лучшее объяснение, особенно когда отвечаете на старые вопросы.
help-info.de

4

Не уверен насчет скорости, но stringification+ indexOfэто простой подход. Начните с превращения вашего массива в строку:

let strMyArray = JSON.stringify(myArray);

Тогда для серии пар атрибут-значение вы можете использовать:

if (strMyArray.indexOf('"name":"tom"') === -1 && strMyArray.indexOf('"text":"tasty"') === -1) {
   myArray.push({ name: "tom", text: "tasty" });
}

Найти весь объект проще:

if (strMyArray.indexOf(JSON.stringify(objAddMe) === -1) { 
   myArray.push(objAddMe);
}

3

В случае, если вам нужно что-то простое, не желая расширять прототип Array:

// Example array
var array = [{id: 1}, {id: 2}, {id: 3}];

function pushIfNew(obj) {
  for (var i = 0; i < array.length; i++) {
    if (array[i].id === obj.id) { // modify whatever property you need
      return;
    }
  }
  array.push(obj);
}

3

В случае, если у кого-то есть менее сложные требования, вот моя адаптация ответа для простого строкового массива:

Array.prototype.pushIfNotExist = function(val) {
    if (typeof(val) == 'undefined' || val == '') { return; }
    val = $.trim(val);
    if ($.inArray(val, this) == -1) {
        this.push(val);
    }
};

Обновление: заменен indexOf и дополнен альтернативами jQuery для совместимости с IE8.


это хорошее решение, но зачем использовать отделку?
Деян Дозет

2

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

var newItem = {'unique_id': 123};
var searchList = [{'unique_id' : 123}, {'unique_id' : 456}];

hasDuplicate = searchList
   .map(function(e){return e.unique_id== newItem.unique_id})
   .reduce(function(pre, cur) {return pre || cur});

if (hasDuplicate) {
   searchList.push(newItem);
} else {
   console.log("Duplicate Item");
}

2

Краткий пример:

if (typeof(arr[key]) === "undefined") {
  arr.push(key);
}

1
Не верно. Мы не заинтересованы в нажатии клавиши, мы хотим нажать пару имя-значение, но только если она еще не существует.
Стефан

2

а это массив объектов у вас есть

a.findIndex(x => x.property=="WhateverPropertyYouWantToMatch") <0 ? 
a.push(objectYouWantToPush) : console.log("response if object exists");

1

Вы можете использовать метод findIndex с функцией обратного вызова и его параметром this.

Примечание: старые браузеры не знают findIndex, но полифилл доступен.

Пример кода (позаботьтесь о том, чтобы в исходном вопросе новый объект выдвигался, только если ни один из его данных не находится в предварительно выдвинутых объектах):

var a=[{name:"tom", text:"tasty"}], b;
var magic=function(e) {
    return ((e.name == this.name) || (e.text == this.text));
};

b={name:"tom", text:"tasty"};
if (a.findIndex(magic,b) == -1)
    a.push(b); // nothing done
b={name:"tom", text:"ugly"};
if (a.findIndex(magic,b) == -1)
    a.push(b); // nothing done
b={name:"bob", text:"tasty"};
if (a.findIndex(magic,b) == -1)
    a.push(b); // nothing done
b={name:"bob", text:"ugly"};
if (a.findIndex(magic,b) == -1)
    a.push(b); // b is pushed into a

1

Я полагаю, что уже слишком поздно, чтобы ответить здесь, но это то, что я наконец-то придумал для почтового менеджера, которого я написал. Работы это все что мне нужно.

window.ListManager = [];
$('#add').click(function(){
//Your Functionality
  let data =Math.floor(Math.random() * 5) + 1 
  
  if (window.ListManager.includes(data)){
      console.log("data exists in list")
  }else{
       window.ListManager.push(data);
  }
  
  
  $('#result').text(window.ListManager);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>Unique List</h1>

<p id="result"></p>
<button id="add">Add to List</button>


0

Это рабочая функция для сравнения объектов. В некоторых случаях у вас может быть много полей для сравнения. Просто зациклите массив и вызовите эту функцию с существующими элементами и новым элементом.

 var objectsEqual = function (object1, object2) {
        if(!object1 || !object2)
            return false;
        var result = true;
        var arrayObj1 = _.keys(object1);
        var currentKey = "";
        for (var i = 0; i < arrayObj1.length; i++) {
            currentKey = arrayObj1[i];
            if (object1[currentKey] !== null && object2[currentKey] !== null)
                if (!_.has(object2, currentKey) ||
                    !_.isEqual(object1[currentKey].toUpperCase(), object2[currentKey].toUpperCase()))
                    return false;
        }
        return result;
    };

0

Здесь у вас есть способ сделать это в одну строку для двух массивов:

const startArray = [1,2,3,4]
const newArray = [4,5,6]

const result = [...startArray, ...newArray.filter(a => !startArray.includes(a))]

console.log(result);
//Result: [1,2,3,4,5,6]

-1

Вы можете использовать jQuery grep и нажать, если нет результатов: http://api.jquery.com/jQuery.grep/

Это в основном то же решение, что и в решении «расширение прототипа», но без расширения (или загрязнения) прототипа.


-2

Вы можете проверить массив с помощью foreach, а затем вытолкнуть элемент, если он существует, иначе добавить новый элемент ...

образец newItemValue & submitFields являются ключом, парами значений

> //submitFields existing array
>      angular.forEach(submitFields, function(item) {
>                   index++; //newItemValue new key,value to check
>                     if (newItemValue == item.value) {
>                       submitFields.splice(index-1,1);
>                         
>                     } });

                submitFields.push({"field":field,"value":value});
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.