Cách hiệu quả nhất để thêm một giá trị vào một mảng


268

Giả sử tôi có một mảng có kích thước N(ở đâu N > 0), có cách nào hiệu quả hơn để chuẩn bị cho mảng không yêu cầu các bước O (N + 1) không?

Về mã, về cơ bản, những gì tôi hiện đang làm là

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

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

  return newArray;
}

nhiều như tôi yêu thích các danh sách và con trỏ được liên kết Tôi cảm thấy như thể phải có một cách hiệu quả hơn để làm mọi thứ bằng cách sử dụng các kiểu dữ liệu JS gốc
samccone

@samccone: Vâng bỏ qua nhận xét của tôi xin lỗi, tôi nghĩ bạn đã nói Java: P
GWW

3
Java, JavaScript, C hoặc Python, không quan trọng bằng ngôn ngữ nào: sự cân bằng phức tạp giữa các mảng so với các danh sách được liên kết là như nhau. Danh sách được liên kết có thể khá khó sử dụng trong JS vì không có lớp dựng sẵn cho chúng (không giống như Java), nhưng nếu điều bạn thực sự muốn là thời gian chèn O (1), thì bạn muốn có một danh sách được liên kết.
mgiuca

1
Có phải là một yêu cầu để sao chép nó?
Ryan Florence

1
Nếu đó là một yêu cầu để sao chép nó, thì unshift là không phù hợp, vì nó sẽ làm thay đổi mảng ban đầu và không tạo ra một bản sao.
mgiuca

Câu trả lời:


493

Tôi không chắc về hiệu quả hơn về mặt big-O nhưng chắc chắn sử dụng unshiftphương pháp này ngắn gọn hơn:

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

[Biên tập]

Điểm chuẩn jsPerf này cho thấy unshifttốc độ nhanh hơn trong ít nhất một vài trình duyệt, bất kể hiệu suất big-O có thể khác nhau nếu bạn đồng ý với việc sửa đổi mảng tại chỗ. Nếu bạn thực sự không thể thay đổi mảng ban đầu thì bạn sẽ làm một cái gì đó như đoạn mã dưới đây, dường như không nhanh hơn giải pháp của bạn:

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

[Chỉnh sửa 2]

Để hoàn thiện, có thể sử dụng hàm sau thay cho ví dụ của OP prependArray(...)để tận dụng unshift(...)phương thức 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
Ai quyết định gọi prepend" unshift"?
Scott Stafford

12
@ScottStafford unshiftnghe có vẻ phù hợp hơn cho một hoạt động mảng như vậy (nó di chuyển các phần tử ... ít nhiều về mặt vật lý). prependsẽ phù hợp hơn với các danh sách được liên kết, nơi bạn thực sự chuẩn bị các yếu tố.
CamilB

12
unshift là chức năng bổ sung để thay đổi. Gọi nó là "trả trước" sẽ là sự lựa chọn kỳ lạ. Unshift là để thay đổi như đẩy là để bật.
Ngài Robert

9
Chỉ có một vấn đề với giải pháp này. Không unshift () trả về độ dài của nó , và không phải là mảng như trong câu trả lời này? w3schools.com/jsref/jsref_unshift.asp
iDVB

4
pushunshiftcả hai đều chứa u, trong khi popshiftkhông có.
Qian Chen

70

Với ES6, giờ đây bạn có thể sử dụng toán tử trải rộng để tạo một mảng mới với các phần tử mới của bạn được chèn trước các phần tử gốc.

// 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]);

Cập nhật 2018-08-17: Hiệu suất

Tôi dự định câu trả lời này để trình bày một cú pháp thay thế mà tôi nghĩ là dễ nhớ và súc tích hơn. Cần lưu ý rằng theo một số điểm chuẩn (xem câu trả lời khác này ), cú pháp này chậm hơn đáng kể. Điều này có lẽ sẽ không quan trọng trừ khi bạn đang thực hiện nhiều thao tác này trong một vòng lặp.


4
Điều này sẽ được bình chọn vào năm 2017. Nó ngắn gọn. Bằng cách sử dụng trải rộng, nó mang lại luồng mới, có thể hữu ích để xâu chuỗi các sửa đổi tiếp theo. Nó cũng thuần túy (có nghĩa là a và b không bị ảnh hưởng)
Simon

Bạn đã không đề cập đến thời gian thực hiện so vớiunshift
Shashank Vivek

Cảm ơn @ShashankVivek. Tôi dự định câu trả lời này để trình bày một cú pháp thay thế mà tôi nghĩ là dễ nhớ và súc tích hơn. Tôi sẽ cập nhật.
Frank Tan

@FrankTan: Chúc mừng :) +1
Shashank Vivek

46

Nếu bạn đang chuẩn bị một mảng ở phía trước của một mảng khác, thì chỉ cần sử dụng sẽ hiệu quả hơn concat. Vì thế:

var newArray = values.concat(oldArray);

Nhưng đây vẫn sẽ là O (N) trong kích thước của oldArray. Tuy nhiên, nó hiệu quả hơn so với việc lặp lại thủ công trên oldArray. Ngoài ra, tùy thuộc vào các chi tiết, nó có thể giúp bạn, bởi vì nếu bạn định trả trước nhiều giá trị, tốt hơn hết là đặt chúng vào một mảng trước và sau đó ghép oldArray vào cuối, thay vì trả trước từng giá trị riêng lẻ.

Không có cách nào để làm tốt hơn O (N) về kích thước của oldArray, bởi vì các mảng được lưu trữ trong bộ nhớ liền kề với phần tử đầu tiên ở một vị trí cố định. Nếu bạn muốn chèn trước phần tử đầu tiên, bạn cần di chuyển tất cả các phần tử khác. Nếu bạn cần một cách giải quyết vấn đề này, hãy làm những gì @GWW đã nói và sử dụng danh sách được liên kết hoặc cấu trúc dữ liệu khác.


2
Ồ vâng, tôi quên mất unshift. Nhưng lưu ý rằng a) làm thay đổi oldArray trong khi concatkhông (vì vậy cái nào tốt hơn cho bạn tùy thuộc vào tình huống) và b) nó chỉ chèn một phần tử.
mgiuca

1
Wow, đó là chậm hơn nhiều. Như tôi đã nói, nó đang tạo một bản sao của mảng (và cũng tạo ra một mảng mới [0]), trong khi unshift đang làm biến đổi nó tại chỗ. Nhưng cả hai nên là O (N). Cũng cổ vũ cho liên kết đến trang web đó - trông rất tiện dụng.
mgiuca

2
nó tốt cho oneliner, vì concat trả về mảng và unshift trả về độ dài mới
Z. Khullah

32

Nếu bạn muốn thêm vào mảng (a1 với mảng a2), bạn có thể sử dụng như sau:

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

6

f bạn cần giữ nguyên mảng cũ, cắt mảng cũ và chuyển (các) giá trị mới sang đầu lát.

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

Tôi có một số thử nghiệm mới về các phương pháp chuẩn bị khác nhau. Đối với các mảng nhỏ (<1000 elems), nhà lãnh đạo dành cho chu trình kết hợp với phương pháp đẩy. Đối với các mảng lớn, phương pháp Unshift trở thành người dẫn đầu.

Nhưng tình huống này chỉ thực sự dành cho trình duyệt Chrome. Trong Firefox unshift có một tối ưu hóa tuyệt vời và nhanh hơn trong mọi trường hợp.

ES6 lây lan chậm hơn 100 lần trong tất cả các trình duyệt.

https://jsbench.me/cgjfc79bgx/1


Câu trả lời chính xác! Nhưng để đảm bảo bạn không bị mất một phần của nó, bạn có thể sao chép các trường hợp thử nghiệm của mình tại đây (có thể với một vài kết quả chạy mẫu) trong trường hợp, ví dụ, jsbench sẽ biến mất.
ruffin

3

Có một phương pháp đặc biệt:

a.unshift(value);

Nhưng nếu bạn muốn thêm một số phần tử vào mảng thì sẽ nhanh hơn khi sử dụng phương thức như vậy:

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
Không ... unshift sẽ thêm mảng làm đối số đầu tiên: var a = [4, 5]; a.unshift ([1,2,3]); console.log (a); // -> [[4, 5], 1, 2, 3]
bjornd

4
Bạn nói đúng! Bạn sẽ cần sử dụngArray.prototype.unshift.apply(a,b);
david


1

Gọi unshiftchỉ trả về độ dài của mảng mới. Vì vậy, để thêm một phần tử vào đầu và trả về một mảng mới, tôi đã làm điều này:

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

hoặc đơn giản với toán tử trải rộng:

[ newVal, ...array ]

Bằng cách này, mảng ban đầu vẫn còn nguyên.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.