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


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.