วิธีที่มีประสิทธิภาพที่สุดในการเพิ่มมูลค่าให้กับอาร์เรย์


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
มันเป็นความต้องการที่จะโคลนมัน?
Ryan Florence

1
ถ้ามันเป็นความต้องการที่จะโคลนมัน unshift ไม่เหมาะสมเพราะมันจะกลายพันธุ์อาร์เรย์เดิมและไม่สร้างสำเนา
mgiuca

คำตอบ:


492

ฉันไม่แน่ใจเกี่ยวกับประสิทธิภาพในแง่ของ 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"
Scott Stafford

12
@ScottStafford unshiftฟังดูเหมาะสมกว่าสำหรับการดำเนินการอาร์เรย์ (มันย้ายองค์ประกอบ ... มากหรือน้อยกว่าทางร่างกาย) prependจะเหมาะสมกว่ากับรายการที่ลิงก์ซึ่งคุณเติมองค์ประกอบไว้อย่างแท้จริง
CamilB

12
unshift เป็นฟังก์ชั่นเสริมที่จะเปลี่ยน เรียกว่า "prepend" จะเป็นตัวเลือกที่แปลก Unshift คือเปลี่ยนเมื่อพุชพุช
เซอร์โรเบิร์ต

9
ปัญหาเดียวเท่านั้นที่มีวิธีนี้ ไม่ได้เปลี่ยน () คืนความยาวไม่ใช่อาร์เรย์เหมือนในคำตอบนี้หรือไม่ 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
สิ่งนี้ควรได้รับการโหวตเมื่อปี 2560 มันรัดกุม ด้วยการใช้สเปรดจะให้กระแสข้อมูลใหม่ซึ่งจะเป็นประโยชน์ในการโยงการแก้ไขเพิ่มเติม นอกจากนี้ยังบริสุทธิ์ (หมายถึง a และ b ไม่ได้รับผลกระทบ)
Simon

คุณไม่ได้เอ่ยถึง abt เวลาที่เปรียบเทียบกับunshift
Shashank Vivek

ขอบคุณ @ShashankVivek ฉันต้องการคำตอบนี้เพื่อเสนอไวยากรณ์ทางเลือกที่ฉันคิดว่าน่าจดจำและกระชับยิ่งขึ้น ฉันจะอัปเดต
Frank Tan

@ Frankank: ไชโย :) +1
Shashank Vivek

46

หากคุณกำลัง prepending concatอาร์เรย์ไปยังด้านหน้าของอาร์เรย์อื่นก็จะมีประสิทธิภาพมากขึ้นในการใช้งานเพียงแค่ ดังนั้น:

var newArray = values.concat(oldArray);

แต่นี่จะยังคงเป็น O (N) ในขนาดของ oldArray ยังมีประสิทธิภาพมากกว่าการวนซ้ำแบบเก่าด้วยตนเอง นอกจากนี้ยังขึ้นอยู่กับรายละเอียดซึ่งอาจช่วยคุณได้เพราะถ้าคุณจะเติมค่าหลายค่าจะเป็นการดีกว่าที่จะใส่เข้าไปในอาร์เรย์ก่อนจากนั้นจึงต่อท้าย oldArray ให้เสร็จสิ้นแทนที่จะเติมแต่ละรายการเป็นรายบุคคล

ไม่มีวิธีที่จะทำได้ดีกว่า O (N) ในขนาดของ oldArray เพราะอาร์เรย์จะถูกเก็บไว้ในหน่วยความจำต่อเนื่องพร้อมองค์ประกอบแรกในตำแหน่งคงที่ หากคุณต้องการแทรกก่อนองค์ประกอบแรกคุณต้องย้ายองค์ประกอบอื่น ๆ ทั้งหมด หากคุณต้องการวิธีนี้ให้ทำสิ่งที่ @GWW พูดและใช้รายการที่เชื่อมโยงหรือโครงสร้างข้อมูลอื่น


2
โอ้ใช่ฉันลืมไปunshiftแล้ว แต่โปรดทราบว่าก) ที่กลายพันธุ์ 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

ถ้าคุณต้องการรักษาอาเรย์เก่าให้เชือดอันเก่าแล้วปลดค่าใหม่ไปที่จุดเริ่มต้นของเชือด

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

ฉันมีการทดสอบใหม่ ๆ ในวิธีการเตรียม สำหรับอาร์เรย์ขนาดเล็ก (<1,000 elems) ผู้นำจะใช้สำหรับวงรอบพร้อมกับวิธีการพุช สำหรับอาร์เรย์ขนาดใหญ่วิธี 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);
david


1

การโทรunshiftจะส่งคืนความยาวของอาร์เรย์ใหม่เท่านั้น ดังนั้นเพื่อเพิ่มองค์ประกอบในการเริ่มต้นและเพื่อกลับอาร์เรย์ใหม่ฉันได้:

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

หรือเพียงแค่กับผู้ประกอบการแพร่กระจาย:

[ newVal, ...array ]

ด้วยวิธีนี้อาร์เรย์ดั้งเดิมจะไม่ถูกแตะต้อง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.