Самый эффективный способ добавить значение в массив


268

Предполагая, что у меня есть массив, размер которого N(где N > 0), есть ли более эффективный способ присоединения к массиву, который не требует O (N + 1) шагов?

В коде, по сути, то, что я сейчас делаю, это

function prependArray(value, oldArray) {
  var newArray = new Array(value);

  for(var i = 0; i < oldArray.length; ++i) {
    newArray.push(oldArray[i]);
  }

  return newArray;
}

Как бы мне ни нравились связанные списки и указатели, я чувствую, что должен быть более эффективный способ сделать что-то, используя родные типы данных JS
— samccone

@samccone: Да, не обращайте внимания на мой комментарий, извините, я думал, что вы сказали Java: P
— GWW

3
Java, JavaScript, C или Python, не важно, на каком языке: компромисс между массивами и связанными списками одинаков. Связанные списки, вероятно, довольно громоздки в JS, потому что для них нет встроенного класса (в отличие от Java), но если вам действительно нужно время вставки O (1), тогда вам нужен связанный список.
— mgiuca

1
Это требование клонировать его?
— Райан Флоренс

1
Если необходимо клонировать его, то unshift неуместен, так как он будет мутировать исходный массив, а не создавать копию.
— mgiuca

Ответы:


493

Я не уверен насчет более эффективного с точки зрения big-O, но, конечно, использование unshiftметода более лаконично:

var a = [1, 2, 3, 4];
a.unshift(0);
a; // => [0, 1, 2, 3, 4]

[Редактировать]

Этот бенчмарк jsPerf показывает, что он unshiftработает прилично быстрее, по крайней мере, в нескольких браузерах, независимо от возможной разной производительности big-O, если вы согласны с изменением массива на месте. Если вы действительно не можете изменить исходный массив, сделайте что-то вроде приведенного ниже фрагмента, который, похоже, не будет значительно быстрее, чем ваше решение:

a.slice().unshift(0); // Use "slice" to avoid mutating "a".

[Редактировать 2]

Для полноты можно использовать следующую функцию вместо примера OP, prependArray(...)чтобы воспользоваться преимуществом unshift(...)метода Array :

function prepend(value, array) {
  var newArray = array.slice();
  newArray.unshift(value);
  return newArray;
}

var x = [1, 2, 3];
var y = prepend(0, x);
y; // => [0, 1, 2, 3];
x; // => [1, 2, 3];

190
Кто решил позвонить prepend" unshift"?
— Скотт Стаффорд

12
@ScottStafford unshiftзвучит более подходящим для такой операции с массивом (он перемещает элементы ... более или менее физически). prependбыло бы более подходящим для связанных списков, где вы буквально добавляете элементы.
— CamilB

12
unshift является дополнительной функцией для переключения. Называть это «prepend» было бы странным выбором. Unshift - это сдвиг, как толчок, чтобы выскочить.
— Сэр Роберт

9
Только одна проблема с этим решением. Разве unshift () не возвращает его длину , а не массив, как в этом ответе? w3schools.com/jsref/jsref_unshift.asp
— iDVB

4
pushи unshiftоба содержат u, тогда как popи shiftне имеют.
— Цянь Чен

70

С ES6 вы можете теперь использовать оператор распространения, чтобы создать новый массив с вашими новыми элементами, вставленными перед исходными элементами.

// Prepend a single item.
const a = [1, 2, 3];
console.log([0, ...a]);

// Prepend an array.
const a = [2, 3];
const b = [0, 1];
console.log([...b, ...a]);

Обновление 2018-08-17: Производительность

Я намеревался этот ответ представить альтернативный синтаксис, который я считаю более запоминающимся и лаконичным. Следует отметить, что согласно некоторым тестам (см. Этот другой ответ ) этот синтаксис значительно медленнее. Это, вероятно, не будет иметь значения, если вы не выполняете многие из этих операций в цикле.


4
Это должно быть проголосовано с 2017 года. Это сжато. Используя распространение, он возвращает новый поток, который может быть полезен для дальнейшей модификации. Это также чисто (имеется в виду, что a и b не затронуты)
— Симон

Вы не упомянули о времени, проведенном по сравнению сunshift
— Шашанк Вивек

Спасибо @ShashankVivek. Я намеревался этот ответ представить альтернативный синтаксис, который я считаю более запоминающимся и лаконичным. Я буду обновлять.
— Фрэнк Тан

@FrankTan: Приветствия :) +1
— Шашанк Вивек

46

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

var newArray = values.concat(oldArray);

Но это все равно будет O (N) в размере oldArray. Тем не менее, это более эффективно, чем ручная итерация по oldArray. Кроме того, в зависимости от деталей, это может помочь вам, потому что, если вы собираетесь добавить много значений, лучше сначала поместить их в массив, а затем в конце объединить oldArray, а не добавлять каждое из них по отдельности.

Нет никакого способа сделать лучше, чем O (N) в размере oldArray, потому что массивы хранятся в непрерывной памяти с первым элементом в фиксированной позиции. Если вы хотите вставить перед первым элементом, вам нужно переместить все остальные элементы. Если вам нужно обойти это, сделайте то, что сказал @GWW, и используйте связанный список или другую структуру данных.


2
О да, я забыл о unshift. Но обратите внимание, что a) он мутирует oldArray, а concatне (так, какой из них лучше для вас зависит от ситуации), и b) он вставляет только один элемент.
— mgiuca

1
Вау, это намного медленнее. Ну, как я уже сказал, он делает копию массива (а также создает новый массив [0]), тогда как unshift изменяет его на месте. Но оба должны быть O (N). Также приветствует ссылку на этот сайт - выглядит очень удобно.
— mgiuca

2
это хорошо для oneliner, так как concat возвращает массив, а unshift возвращает новую длину
— Z. Khullah

32

Если вы хотите добавить массив (a1 к массиву a2), вы можете использовать следующее:

var a1 = [1, 2];
var a2 = [3, 4];
Array.prototype.unshift.apply(a1, a2);
console.log(a1);
// => [3, 4, 1, 2]

6

f вам нужно сохранить старый массив, нарезать старый и отменить новое (ые) значение (я) в начале слайса.

var oldA=[4,5,6];
newA=oldA.slice(0);
newA.unshift(1,2,3)

oldA+'\n'+newA

/*  returned value:
4,5,6
1,2,3,4,5,6
*/

4

У меня есть несколько свежих тестов различных методов приготовления. Для небольших массивов (<1000 элементов) лидером является цикл, связанный с методом push. Для огромных массивов метод Unshift становится лидером.

Но эта ситуация актуальна только для браузера Chrome. В Firefox unshift имеет потрясающую оптимизацию и работает быстрее во всех случаях.

ES6 распространяется в 100+ раз медленнее во всех браузерах.

https://jsbench.me/cgjfc79bgx/1


Отличный ответ! Но чтобы гарантировать, что вы не потеряете часть этого, вы можете воспроизвести свои тестовые примеры здесь (возможно, с несколькими результатами пробных запусков) в случае, например, jsbench пропадет.
— ruffin

3

Есть специальный метод:

a.unshift(value);

Но если вы хотите добавить несколько элементов в массив, будет быстрее использовать такой метод:

var a = [1, 2, 3],
    b = [4, 5];

function prependArray(a, b) {
    var args = b;
    args.unshift(0);
    args.unshift(0);
    Array.prototype.splice.apply(a, args);
}

prependArray(a, b);
console.log(a); // -> [4, 5, 1, 2, 3]

2
Нет ... unshift добавит массив в качестве первого аргумента: var a = [4, 5]; a.unshift ([1,2,3]); console.log (а); // -> [[4, 5], 1, 2, 3]
— bjornd

4
Ты прав! Вы должны будете использоватьArray.prototype.unshift.apply(a,b);
— Дэвид


1

Вызов unshiftтолько возвращает длину нового массива. Итак, чтобы добавить элемент в начале и вернуть новый массив, я сделал это:

let newVal = 'someValue';
let array = ['hello', 'world'];
[ newVal ].concat(array);

или просто с оператором распространения:

[ newVal, ...array ]

Таким образом, исходный массив остается нетронутым.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.