Найефективніший спосіб додати значення до масиву


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
Якщо це вимога клонувати її, то невміння відхиляється недоцільно, оскільки воно буде мутувати вихідний масив і не створювати копію.
mgiuca

Відповіді:


492

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

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

[Редагувати]

Цей орієнтир jsPerf показує, що unshiftвін пристойно швидший принаймні в декількох браузерах, незалежно від можливої ​​різної продуктивності великого виходу, якщо ви не в змозі змінити масив на місці. Якщо ви дійсно не можете вимкнути оригінальний масив, ви зробите щось на зразок фрагмента нижче, який, здається, не є значно швидшим, ніж ваше рішення:

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

[Редагувати 2]

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

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
недвинутость - це додаткова функція зміщення. Називати це "препендером" було б дивним вибором. Розміщення - це зміщення, як натискання на спливання.
Сер Роберт

9
Лише одне питання з цим рішенням. Не повертає ("Shihift") довжину , а не масив, як у цій відповіді? w3schools.com/jsref/jsref_unshift.asp
iDVB

4
pushі unshiftобидва містять u, тоді як popі shiftне мають.
Qian Chen

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. Але зауважте, що а) який вимикає oldArray, тоді як concatвін не (так, який краще для вас залежить від ситуації), і b) він вставляє лише один елемент.
mgiuca

1
Нічого собі, це набагато повільніше. Ну, як я вже сказав, він робить копію масиву (а також створює новий масив [0]), тоді як невмилий змінює його на місці. Але обидва повинні бути O (N). Також привітатись за посилання на цей сайт - виглядає дуже зручно.
mgiuca

2
це корисно для oneliner, оскільки concat повертає масив, а невмилий повертає нову довжину
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 nonhift має дивовижну оптимізацію і швидше у всіх випадках.

Поширення 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
Ні ... невмикання додасть масив як перший аргумент: var a = [4, 5]; a.змінення ([1,2,3]); console.log (a); // -> [[4, 5], 1, 2, 3]
bjornd

4
Ви праві! Вам потрібно було б скористатисяArray.prototype.unshift.apply(a,b);
David


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.