Lặp lại chuỗi - Javascript


271

Phương pháp tốt nhất hoặc ngắn gọn nhất để trả về một chuỗi lặp lại một số lần tùy ý là gì?

Sau đây là bức ảnh đẹp nhất của tôi cho đến nay:

function repeat(s, n){
    var a = [];
    while(a.length < n){
        a.push(s);
    }
    return a.join('');
}

5
Hơn 10 năm trước, có một giải pháp nổi tiếng của tôi cho vấn đề này và tôi đã sử dụng làm ví dụ trong một bài viết tối ưu hóa JavaScript vài tháng trước khi bạn hỏi câu hỏi này: webreference.com/programming/javascript/jkm3/3 .html Rõ ràng, hầu hết mọi người đã quên mã đó và tôi không thấy bất kỳ giải pháp nào tốt dưới đây tốt như của tôi. Thuật toán tốt nhất trông giống như được lấy từ mã của tôi; ngoại trừ do sự hiểu lầm về cách thức hoạt động của mã của tôi, nó thực hiện thêm một bước nối ghép theo cấp số nhân được loại bỏ trong bản gốc của tôi bằng một vòng lặp đặc biệt.
Joseph Myers

10
Không ai nâng giải pháp của Joseph. Thuật toán là 3700 năm tuổi. Chi phí của bước thêm là không đáng kể. Và bài viết này chứa lỗi và quan niệm sai lầm về nối chuỗi trong Javascript. Đối với bất kỳ ai quan tâm đến cách Javascript thực sự xử lý các chuỗi bên trong, hãy xem Rope .
artistoex

4
Dường như không ai nhận thấy rằng lặp lại chuỗi protoype được xác định và triển khai, ít nhất là trong firefox.
kennebec

3
@kennebec: Vâng, đó là một tính năng EcmaScript 6 không xuất hiện khi câu hỏi này được hỏi. Bây giờ nó được hỗ trợ khá tốt.
rvighne

3
@rvighne - Tôi vừa mới kiểm tra kangax.github.io/compat-table/es6/#String.prototype.repeat Tôi sẽ không xem xét hỗ trợ độc quyền từ firefox và chrome là "được hỗ trợ khá tốt"
aaaaaa

Câu trả lời:


405

Lưu ý cho những người đọc mới: Câu trả lời này là cũ và không thực tế lắm - nó chỉ "thông minh" vì nó sử dụng công cụ Array để hoàn thành công việc String. Khi tôi viết "ít quy trình", tôi chắc chắn có nghĩa là "ít mã hơn" bởi vì, như những người khác đã lưu ý trong các câu trả lời tiếp theo, nó hoạt động như một con lợn. Vì vậy, đừng sử dụng nó nếu tốc độ quan trọng với bạn.

Tôi sẽ đặt hàm này trực tiếp lên đối tượng String. Thay vì tạo một mảng, điền nó và nối nó với một char trống, chỉ cần tạo một mảng có độ dài phù hợp và nối nó với chuỗi mong muốn của bạn. Kết quả tương tự, quá trình ít hơn!

String.prototype.repeat = function( num )
{
    return new Array( num + 1 ).join( this );
}

alert( "string to repeat\n".repeat( 4 ) );

36
Tôi cố gắng không mở rộng các đối tượng bản địa, nhưng nếu không thì đây là một giải pháp đẹp. Cảm ơn!
brad

34
@ brad - tại sao không? Bạn muốn làm ô nhiễm không gian tên toàn cầu bằng một hàm có một ngôi nhà được xác định khá rõ (đối tượng String)?
Peter Bailey

16
Trên thực tế, cả hai đối số của bạn cũng áp dụng cho không gian tên toàn cầu. Nếu tôi sẽ mở rộng một không gian tên và có các xung đột tiềm năng, tôi muốn làm điều đó 1) không phải trong toàn cầu 2) trong một liên kết có liên quan và 3) rất dễ để cấu trúc lại. Điều này có nghĩa là đưa nó vào nguyên mẫu String, không phải trên toàn cầu.
Peter Bailey

11
Một thay đổi tôi thực hiện cho hàm này là đặt parseInt () xung quanh "num", vì nếu bạn có một chuỗi số, bạn có thể có hành vi lạ do cách tung hứng của JS. ví dụ: "chuỗi của tôi" .repeat ("6") == "61"
nickf

19
Nếu bạn không muốn mở rộng các đối tượng gốc, bạn có thể đặt hàm trên đối tượng String thay vào đó, như sau : String.repeat = function(string, num){ return new Array(parseInt(num) + 1).join(string); };. Gọi nó như thế này:String.repeat('/\', 20)
Znarkus

203

Tôi đã thử nghiệm hiệu suất của tất cả các phương pháp đề xuất.

Đây là biến thể nhanh nhất tôi có.

String.prototype.repeat = function(count) {
    if (count < 1) return '';
    var result = '', pattern = this.valueOf();
    while (count > 1) {
        if (count & 1) result += pattern;
        count >>= 1, pattern += pattern;
    }
    return result + pattern;
};

Hoặc là chức năng độc lập :

function repeat(pattern, count) {
    if (count < 1) return '';
    var result = '';
    while (count > 1) {
        if (count & 1) result += pattern;
        count >>= 1, pattern += pattern;
    }
    return result + pattern;
}

Nó dựa trên thuật toán artistoex . Nó thực sự rất nhanh. Và càng lớn count, nó càng đi nhanh hơn so với new Array(count + 1).join(string)cách tiếp cận truyền thống .

Tôi chỉ thay đổi 2 điều:

  1. thay thế pattern = thisbằng pattern = this.valueOf()(xóa một chuyển đổi loại rõ ràng);
  2. đã thêm if (count < 1)kiểm tra từ nguyên mẫu vào đầu hàm để loại trừ các hành động không cần thiết trong trường hợp đó.
  3. tối ưu hóa được áp dụng từ câu trả lời của Dennis (tăng tốc 5 - 7%)

CẬP NHẬT

Tạo một sân chơi thử nghiệm hiệu suất nhỏ ở đây cho những người quan tâm.

biến count~ 0 .. 100:

Biểu đồ hiệu suất

hằng số count= 1024:

Biểu đồ hiệu suất

Sử dụng nó và làm cho nó thậm chí nhanh hơn nếu bạn có thể :)


4
Công việc tốt đẹp! Tôi nghĩ rằng count < 1trường hợp thực sự tối ưu hóa không cần thiết.
JayVee

Thuật toán xuất sắc O (log N). Cảm ơn đã tối ưu hóa tuyệt vời với valueOf ()
vp_arth

2
Liên kết hình ảnh đã chết.
Benjamin Gruenbaum

Liên kết là tốt. Có thể không có sẵn tạm thời
mất

JSFiddle thử nghiệm không hoạt động chính xác nữa; nó dường như chỉ tiếp tục chạy chức năng đầu tiên nhiều lần (để nó hoạt động trong nửa giờ để chắc chắn)
RevanProdigalKnight

47

Vấn đề này là một vấn đề tối ưu hóa nổi tiếng / "cổ điển" đối với JavaScript, do thực tế là các chuỗi JavaScript là "bất biến" và thêm vào đó bằng cách ghép một ký tự duy nhất vào một chuỗi yêu cầu tạo, bao gồm cấp phát bộ nhớ cho và sao chép vào , một chuỗi hoàn toàn mới.

Thật không may, câu trả lời được chấp nhận trên trang này là sai, trong đó "sai" có nghĩa là hệ số hiệu suất 3x cho các chuỗi một ký tự đơn giản và 8x-97x cho các chuỗi ngắn lặp lại nhiều lần hơn, đến 300 lần cho các câu lặp lại và vô cùng sai khi lấy giới hạn của các tỷ lệ phức tạp của các thuật toán nđi đến vô cùng. Ngoài ra, có một câu trả lời khác trên trang này gần như đúng (dựa trên một trong nhiều thế hệ và biến thể của giải pháp chính xác lưu hành trên Internet trong 13 năm qua). Tuy nhiên, giải pháp "gần như đúng" này bỏ lỡ một điểm chính của thuật toán chính xác gây ra sự suy giảm hiệu suất 50%.

Kết quả hiệu suất JS cho câu trả lời được chấp nhận, câu trả lời khác có hiệu suất cao nhất (dựa trên phiên bản xuống cấp của thuật toán gốc trong câu trả lời này) và câu trả lời này sử dụng thuật toán của tôi được tạo ra cách đây 13 năm

~ Tháng 10 năm 2000 tôi đã xuất bản một thuật toán cho vấn đề chính xác này đã được điều chỉnh, sửa đổi rộng rãi, sau đó cuối cùng được hiểu và bị lãng quên. Để khắc phục vấn đề này, vào tháng 8 năm 2008, tôi đã xuất bản một bài viết http://www.webreference.com/programming/javascript/jkm3/3.html giải thích thuật toán và sử dụng nó như một ví dụ đơn giản về tối ưu hóa JavaScript cho mục đích chung. Đến bây giờ, Web Reference đã xem xét thông tin liên lạc của tôi và thậm chí tên của tôi từ bài viết này. Và một lần nữa, thuật toán đã được điều chỉnh, sửa đổi rộng rãi, sau đó được hiểu kém và bị lãng quên.

Thuật toán JavaScript lặp lại / nhân chuỗi gốc của Joseph Myers, tuần hoàn Y2K như một hàm nhân văn bản trong Text.js; được xuất bản vào tháng 8 năm 2008 dưới dạng này bởi Web Reference: http://www.webreference.com/programming/javascript/jkm3/3.html (Bài viết đã sử dụng chức năng này như một ví dụ về tối ưu hóa JavaScript, chỉ dành cho người lạ tên "chuỗiFill3.")

/*
 * Usage: stringFill3("abc", 2) == "abcabc"
 */

function stringFill3(x, n) {
    var s = '';
    for (;;) {
        if (n & 1) s += x;
        n >>= 1;
        if (n) x += x;
        else break;
    }
    return s;
}

Trong vòng hai tháng sau khi xuất bản bài báo đó, câu hỏi tương tự đã được đăng lên Stack Overflow và bay theo radar của tôi cho đến bây giờ, khi rõ ràng thuật toán ban đầu cho vấn đề này một lần nữa bị lãng quên. Giải pháp tốt nhất có sẵn trên trang Stack Overflow này là phiên bản sửa đổi của giải pháp của tôi, có thể được phân tách bằng nhiều thế hệ. Thật không may, các sửa đổi đã phá hỏng sự tối ưu của giải pháp. Trong thực tế, bằng cách thay đổi cấu trúc của vòng lặp từ bản gốc của tôi, giải pháp đã sửa đổi thực hiện một bước bổ sung hoàn toàn không cần thiết của sao chép hàm mũ (do đó nối chuỗi lớn nhất được sử dụng trong câu trả lời thích hợp với chính nó thêm thời gian và sau đó loại bỏ nó).

Dưới đây đảm bảo một cuộc thảo luận về một số tối ưu hóa JavaScript liên quan đến tất cả các câu trả lời cho vấn đề này và vì lợi ích của tất cả.

Kỹ thuật: Tránh tham chiếu đến các đối tượng hoặc thuộc tính đối tượng

Để minh họa cách thức hoạt động của kỹ thuật này, chúng tôi sử dụng hàm JavaScript ngoài đời thực, tạo ra các chuỗi có độ dài bất kỳ là cần thiết. Và như chúng ta sẽ thấy, có thể thêm nhiều tối ưu hóa!

Một chức năng giống như chức năng được sử dụng ở đây là tạo phần đệm để căn chỉnh các cột văn bản, để định dạng tiền hoặc để điền dữ liệu khối lên đến ranh giới. Hàm tạo văn bản cũng cho phép nhập độ dài thay đổi để kiểm tra bất kỳ chức năng nào khác hoạt động trên văn bản. Hàm này là một trong những thành phần quan trọng của mô đun xử lý văn bản JavaScript.

Khi chúng tôi tiến hành, chúng tôi sẽ đề cập đến hai kỹ thuật tối ưu hóa quan trọng nhất trong khi phát triển mã gốc thành một thuật toán tối ưu hóa để tạo chuỗi. Kết quả cuối cùng là một chức năng hiệu suất cao, có độ bền công nghiệp mà tôi đã sử dụng ở mọi nơi - căn chỉnh giá vật phẩm và tổng số trong các mẫu đơn đặt hàng JavaScript, định dạng dữ liệu và định dạng email / tin nhắn văn bản và nhiều cách sử dụng khác.

Mã gốc để tạo chuỗi stringFill1()

function stringFill1(x, n) { 
    var s = ''; 
    while (s.length < n) s += x; 
    return s; 
} 
/* Example of output: stringFill1('x', 3) == 'xxx' */ 

Cú pháp ở đây là rõ ràng. Như bạn có thể thấy, chúng ta đã sử dụng các biến chức năng cục bộ rồi, trước khi tiếp tục tối ưu hóa nhiều hơn.

Xin lưu ý rằng có một tham chiếu vô tội đến một s.lengththuộc tính đối tượng trong mã làm tổn thương hiệu năng của nó. Thậm chí tệ hơn, việc sử dụng thuộc tính đối tượng này làm giảm tính đơn giản của chương trình bằng cách đưa ra giả định rằng người đọc biết về các thuộc tính của các đối tượng chuỗi JavaScript.

Việc sử dụng thuộc tính đối tượng này sẽ phá hủy tính tổng quát của chương trình máy tính. Chương trình giả định rằng xphải là một chuỗi có độ dài một. Điều này giới hạn việc áp dụng stringFill1()hàm cho bất cứ điều gì ngoại trừ việc lặp lại các ký tự đơn. Ngay cả các ký tự đơn cũng không thể được sử dụng nếu chúng chứa nhiều byte như thực thể HTML &nbsp;.

Vấn đề tồi tệ nhất gây ra bởi việc sử dụng thuộc tính đối tượng không cần thiết này là hàm tạo ra một vòng lặp vô hạn nếu được kiểm tra trên một chuỗi đầu vào trống x. Để kiểm tra tổng quát, áp dụng một chương trình với lượng đầu vào nhỏ nhất có thể. Một chương trình gặp sự cố khi được yêu cầu vượt quá dung lượng bộ nhớ khả dụng có một lý do. Một chương trình như thế này bị sập khi được yêu cầu sản xuất không có gì là không thể chấp nhận được. Đôi khi mã đẹp là mã độc.

Đơn giản có thể là một mục tiêu mơ hồ của lập trình máy tính, nhưng nói chung là không. Khi một chương trình thiếu bất kỳ mức độ tổng quát hợp lý nào, sẽ không hợp lệ để nói, "Chương trình này đủ tốt cho đến khi nó đi." Như bạn có thể thấy, việc sử dụng thuộc string.lengthtính sẽ ngăn chương trình này hoạt động trong cài đặt chung và trên thực tế, chương trình không chính xác đã sẵn sàng gây ra sự cố trình duyệt hoặc hệ thống.

Có cách nào để cải thiện hiệu suất của JavaScript này cũng như chăm sóc hai vấn đề nghiêm trọng này không?

Tất nhiên. Chỉ cần sử dụng số nguyên.

Mã được tối ưu hóa để tạo chuỗi stringFill2()

function stringFill2(x, n) { 
    var s = ''; 
    while (n-- > 0) s += x; 
    return s; 
} 

Mã thời gian để so sánh stringFill1()stringFill2()

function testFill(functionToBeTested, outputSize) { 
    var i = 0, t0 = new Date(); 
    do { 
        functionToBeTested('x', outputSize); 
        t = new Date() - t0; 
        i++; 
    } while (t < 2000); 
    return t/i/1000; 
} 
seconds1 = testFill(stringFill1, 100); 
seconds2 = testFill(stringFill2, 100); 

Thành công cho đến nay stringFill2()

stringFill1()mất 47.297 micro giây (một phần triệu giây) để điền vào chuỗi 100 byte và stringFill2()mất 27,68 micro giây để làm điều tương tự. Điều đó gần như tăng gấp đôi hiệu suất bằng cách tránh tham chiếu đến một thuộc tính đối tượng.

Kỹ thuật: Tránh thêm chuỗi ngắn vào chuỗi dài

Kết quả trước đây của chúng tôi có vẻ tốt - thực sự rất tốt. Chức năng được cải thiện stringFill2()nhanh hơn nhiều do sử dụng hai tối ưu hóa đầu tiên của chúng tôi. Bạn có tin không nếu tôi nói với bạn rằng nó có thể được cải thiện nhanh hơn nhiều lần so với bây giờ?

Vâng, chúng ta có thể hoàn thành mục tiêu đó. Ngay bây giờ chúng ta cần giải thích làm thế nào chúng ta tránh nối các chuỗi ngắn vào chuỗi dài.

Hành vi ngắn hạn có vẻ khá tốt, so với chức năng ban đầu của chúng tôi. Các nhà khoa học máy tính muốn phân tích "hành vi tiệm cận" của một thuật toán chức năng hoặc chương trình máy tính, có nghĩa là nghiên cứu hành vi dài hạn của nó bằng cách kiểm tra nó với các đầu vào lớn hơn. Đôi khi không thực hiện các bài kiểm tra tiếp theo, người ta không bao giờ nhận thức được các cách mà một chương trình máy tính có thể được cải thiện. Để xem điều gì sẽ xảy ra, chúng ta sẽ tạo một chuỗi 200 byte.

Vấn đề xuất hiện với stringFill2()

Sử dụng chức năng định thời của chúng tôi, chúng tôi thấy rằng thời gian tăng lên 62,54 micro giây cho chuỗi 200 byte, so với 27,68 cho chuỗi 100 byte. Có vẻ như thời gian nên tăng gấp đôi để thực hiện gấp đôi công việc, nhưng thay vào đó, nó tăng gấp ba hoặc gấp bốn lần. Từ kinh nghiệm lập trình, kết quả này có vẻ lạ, bởi vì nếu có bất cứ điều gì, chức năng sẽ nhanh hơn một chút vì công việc được thực hiện hiệu quả hơn (200 byte cho mỗi lệnh gọi hàm thay vì 100 byte cho mỗi lệnh gọi hàm). Vấn đề này liên quan đến một thuộc tính xảo quyệt của các chuỗi JavaScript: Các chuỗi JavaScript là "không thay đổi".

Bất biến có nghĩa là bạn không thể thay đổi một chuỗi khi nó được tạo. Bằng cách thêm vào một byte mỗi lần, chúng tôi không sử dụng thêm một byte nỗ lực. Chúng tôi thực sự đang tạo lại toàn bộ chuỗi cộng thêm một byte.

Trong thực tế, để thêm một byte vào chuỗi 100 byte, phải mất 101 byte công việc. Hãy phân tích ngắn gọn chi phí tính toán để tạo một chuỗi Nbyte. Chi phí thêm byte đầu tiên là 1 đơn vị nỗ lực tính toán. Chi phí thêm byte thứ hai không phải là một đơn vị mà là 2 đơn vị (sao chép byte đầu tiên sang đối tượng chuỗi mới cũng như thêm byte thứ hai). Byte thứ ba yêu cầu chi phí là 3 đơn vị, v.v.

C(N) = 1 + 2 + 3 + ... + N = N(N+1)/2 = O(N^2). Biểu tượng O(N^2)được phát âm là Big O của N bình phương, và nó có nghĩa là chi phí tính toán trong thời gian dài tỷ lệ với bình phương của độ dài chuỗi. Để tạo 100 ký tự cần 10.000 đơn vị công việc và để tạo 200 ký tự cần 40.000 đơn vị công việc.

Đây là lý do tại sao phải mất hơn gấp đôi thời gian để tạo 200 ký tự hơn 100 ký tự. Trong thực tế, nó đã phải mất bốn lần dài như vậy. Kinh nghiệm lập trình của chúng tôi là chính xác ở chỗ công việc đang được thực hiện hiệu quả hơn một chút đối với các chuỗi dài hơn và do đó chỉ mất khoảng ba lần thời gian. Khi tổng phí của lệnh gọi hàm không đáng kể về thời gian tạo chuỗi, thực tế sẽ mất bốn lần thời gian để tạo chuỗi dài gấp đôi.

(Lưu ý lịch sử: Phân tích này không nhất thiết phải áp dụng cho các chuỗi trong mã nguồn, chẳng hạn như html = 'abcd\n' + 'efgh\n' + ... + 'xyz.\n', vì trình biên dịch mã nguồn JavaScript có thể nối các chuỗi lại với nhau trước khi biến chúng thành một đối tượng chuỗi JavaScript. Chỉ vài năm trước, việc triển khai KJS của JavaScript sẽ đóng băng hoặc sập khi tải các chuỗi mã nguồn dài được nối bằng dấu cộng. Vì thời gian tính toán O(N^2)không khó để tạo các trang web làm quá tải trình duyệt Web Konqueror hoặc Safari, sử dụng lõi công cụ JavaScript KJS. đã gặp phải vấn đề này khi tôi đang phát triển một ngôn ngữ đánh dấu và trình phân tích cú pháp ngôn ngữ đánh dấu JavaScript và sau đó tôi phát hiện ra nguyên nhân gây ra sự cố khi tôi viết tập lệnh của mình cho JavaScript Bao gồm.)

Rõ ràng sự xuống cấp nhanh chóng của hiệu suất này là một vấn đề rất lớn. Làm thế nào chúng ta có thể đối phó với nó, với điều kiện là chúng ta không thể thay đổi cách xử lý chuỗi của JavaScript thành các đối tượng bất biến? Giải pháp là sử dụng thuật toán tạo lại chuỗi càng ít lần càng tốt.

Để làm rõ, mục tiêu của chúng tôi là tránh thêm các chuỗi ngắn vào các chuỗi dài, vì để thêm chuỗi ngắn, toàn bộ chuỗi dài cũng phải được sao chép.

Cách thuật toán hoạt động để tránh thêm chuỗi ngắn vào chuỗi dài

Đây là một cách tốt để giảm số lần các đối tượng chuỗi mới được tạo. Ghép các chuỗi dài hơn với nhau để nhiều hơn một byte tại một thời điểm được thêm vào đầu ra.

Chẳng hạn, để tạo một chuỗi độ dài N = 9:

x = 'x'; 
s = ''; 
s += x; /* Now s = 'x' */ 
x += x; /* Now x = 'xx' */ 
x += x; /* Now x = 'xxxx' */ 
x += x; /* Now x = 'xxxxxxxx' */ 
s += x; /* Now s = 'xxxxxxxxx' as desired */

Làm điều này đòi hỏi phải tạo chuỗi có độ dài 1, tạo chuỗi có độ dài 2, tạo chuỗi có độ dài 4, tạo chuỗi có độ dài 8 và cuối cùng, tạo chuỗi có độ dài 9. Chúng ta đã tiết kiệm được bao nhiêu chi phí?

Chi phí cũ C(9) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 9 = 45.

Chi phí mới C(9) = 1 + 2 + 4 + 8 + 9 = 24.

Lưu ý rằng chúng ta phải thêm một chuỗi có độ dài 1 vào chuỗi có độ dài 0, sau đó là chuỗi có độ dài 1 thành chuỗi có độ dài 1, sau đó là chuỗi có độ dài 2 thành chuỗi có độ dài 2, sau đó là chuỗi có độ dài 4 đến một chuỗi có độ dài 4, sau đó là một chuỗi có độ dài 8 đến một chuỗi có độ dài 1, để có được một chuỗi có độ dài 9. Những gì chúng ta đang làm có thể được tóm tắt là tránh thêm chuỗi ngắn vào chuỗi dài hoặc khác các từ, cố gắng nối các chuỗi với nhau có độ dài bằng hoặc gần bằng nhau.

Đối với chi phí tính toán cũ, chúng tôi tìm thấy một công thức N(N+1)/2. Có một công thức cho chi phí mới? Vâng, nhưng nó phức tạp. Điều quan trọng là nó là O(N)như vậy, và vì vậy, nhân đôi độ dài chuỗi sẽ xấp xỉ gấp đôi số lượng công việc thay vì tăng gấp bốn lần.

Mã thực hiện ý tưởng mới này gần như phức tạp như công thức tính chi phí tính toán. Khi bạn đọc nó, hãy nhớ điều đó >>= 1có nghĩa là dịch chuyển sang phải 1 byte. Vì vậy, nếu n = 10011là một số nhị phân, sau đó n >>= 1kết quả trong giá trị n = 1001.

Phần khác của mã bạn có thể không nhận ra là bitwise và toán tử, được viết &. Biểu thức n & 1đánh giá đúng nếu chữ số nhị phân cuối cùng nlà 1 và sai nếu chữ số nhị phân cuối cùng nlà 0.

stringFill3()Chức năng mới hiệu quả cao

function stringFill3(x, n) { 
    var s = ''; 
    for (;;) { 
        if (n & 1) s += x; 
        n >>= 1; 
        if (n) x += x; 
        else break; 
    } 
    return s; 
} 

Nó trông xấu xí đối với mắt chưa được huấn luyện, nhưng hiệu suất của nó không kém gì đáng yêu.

Chúng ta hãy xem chức năng này hoạt động tốt như thế nào. Sau khi xem kết quả, có khả năng bạn sẽ không bao giờ quên sự khác biệt giữa O(N^2)thuật toán và O(N)thuật toán.

stringFill1()mất 88,7 micro giây (một phần triệu giây) để tạo chuỗi 200 byte, stringFill2()mất 62,54 và stringFill3()chỉ mất 4,608. Điều gì làm cho thuật toán này tốt hơn nhiều? Tất cả các hàm đã tận dụng lợi thế của việc sử dụng các biến chức năng cục bộ, nhưng tận dụng các kỹ thuật tối ưu hóa thứ hai và thứ ba đã thêm một cải tiến gấp hai lần vào hiệu suất của stringFill3().

Phân tích sâu hơn

Điều gì làm cho chức năng đặc biệt này thổi sự cạnh tranh ra khỏi nước?

Như tôi đã đề cập, lý do cả hai hàm này stringFill1()stringFill2()chạy chậm là vì các chuỗi JavaScript là bất biến. Bộ nhớ không thể được phân bổ lại để cho phép thêm một byte mỗi lần được thêm vào dữ liệu chuỗi được lưu trữ bởi JavaScript. Mỗi lần thêm một byte vào cuối chuỗi, toàn bộ chuỗi được tạo lại từ đầu đến cuối.

Do đó, để cải thiện hiệu suất của tập lệnh, người ta phải tính toán trước các chuỗi có độ dài dài hơn bằng cách nối hai chuỗi với nhau trước thời hạn và sau đó xây dựng đệ quy độ dài chuỗi mong muốn.

Ví dụ, để tạo một chuỗi byte 16 ký tự, đầu tiên một chuỗi hai byte sẽ được tính toán trước. Sau đó, chuỗi hai byte sẽ được sử dụng lại để tính toán trước chuỗi bốn byte. Sau đó, chuỗi bốn byte sẽ được sử dụng lại để tính toán trước chuỗi tám byte. Cuối cùng, hai chuỗi tám byte sẽ được sử dụng lại để tạo chuỗi 16 byte mới mong muốn. Tổng cộng bốn chuỗi mới đã được tạo, một chiều dài 2, một chiều dài 4, một chiều dài 8 và một chiều dài 16. Tổng chi phí là 2 + 4 + 8 + 16 = 30.

Về lâu dài, hiệu quả này có thể được tính bằng cách thêm theo thứ tự ngược lại và sử dụng chuỗi hình học bắt đầu bằng số hạng đầu tiên a1 = N và có tỷ lệ chung là r = 1/2. Tổng của một loạt hình học được đưa ra bởi a_1 / (1-r) = 2N.

Điều này hiệu quả hơn so với việc thêm một ký tự để tạo một chuỗi mới có độ dài 2, tạo ra một chuỗi mới có độ dài 3, 4, 5, v.v., cho đến khi 16. Thuật toán trước đó sử dụng quy trình thêm một byte mỗi lần và tổng chi phí của nó sẽ là n (n + 1) / 2 = 16 (17) / 2 = 8 (17) = 136.

Rõ ràng, 136 là một con số lớn hơn nhiều so với 30, và vì vậy thuật toán trước đó mất nhiều thời gian hơn nhiều để xây dựng một chuỗi.

Để so sánh hai phương pháp, bạn có thể thấy thuật toán đệ quy nhanh hơn (còn gọi là "chia và chinh phục") trên một chuỗi có độ dài 123.457. Trên máy tính FreeBSD của tôi, thuật toán này, được triển khai trong stringFill3()hàm, tạo ra chuỗi trong 0,001058 giây, trong khi stringFill1()chức năng ban đầu tạo ra chuỗi trong 0,0809 giây. Chức năng mới nhanh hơn 76 lần.

Sự khác biệt về hiệu suất tăng lên khi chiều dài của chuỗi trở nên lớn hơn. Trong giới hạn khi các chuỗi lớn hơn và lớn hơn được tạo ra, hàm ban đầu hoạt động gần như C1thời gian (không đổi) N^2và hàm mới hoạt động như C2thời gian (không đổi) N.

Từ thí nghiệm của chúng tôi, chúng tôi có thể xác định giá trị C1được C1 = 0.0808 / (123457)2 = .00000000000530126997, và giá trị của C2được C2 = 0.001058 / 123457 = .00000000856978543136. Trong 10 giây, chức năng mới có thể tạo ra một chuỗi chứa 1.166.890.359 ký tự. Để tạo cùng chuỗi này, hàm cũ sẽ cần 7.218.384 giây.

Đây là gần ba tháng so với mười giây!

Tôi chỉ trả lời (trễ vài năm) vì giải pháp ban đầu của tôi cho vấn đề này đã trôi nổi trên Internet hơn 10 năm, và rõ ràng vẫn còn ít người hiểu được nó. Tôi nghĩ rằng bằng cách viết một bài báo về nó ở đây tôi sẽ giúp:

Tối ưu hóa hiệu suất cho JavaScript tốc độ cao / Trang 3

Thật không may, một số giải pháp khác được trình bày ở đây vẫn là một số giải pháp sẽ mất ba tháng để tạo ra cùng một lượng đầu ra mà một giải pháp thích hợp tạo ra trong 10 giây.

Tôi muốn dành thời gian để sao chép một phần của bài viết ở đây như một câu trả lời chính tắc trên Stack Overflow.

Lưu ý rằng thuật toán hoạt động tốt nhất ở đây rõ ràng dựa trên thuật toán của tôi và có thể được kế thừa từ sự thích ứng thế hệ thứ 3 hoặc thứ 4 của người khác. Thật không may, các sửa đổi dẫn đến giảm hiệu suất của nó. Biến thể của giải pháp của tôi được trình bày ở đây có lẽ không hiểu for (;;)biểu thức khó hiểu của tôi trông giống như vòng lặp vô hạn chính của máy chủ được viết bằng C và được thiết kế đơn giản để cho phép một câu lệnh ngắt được định vị cẩn thận để điều khiển vòng lặp, cách nhỏ gọn nhất để tránh nhân rộng chuỗi theo thời gian thêm một lần không cần thiết.


4
Câu trả lời này không nên nhận được nhiều upvote. Trước hết, các yêu cầu quyền sở hữu của Joseph là chế giễu. Thuật toán cơ bản là 3700 năm tuổi.
artistoex

2
Thứ hai, nó chứa rất nhiều thông tin sai lệch. Các triển khai Javascript hiện đại thậm chí không chạm vào nội dung của chuỗi khi thực hiện nối (v8 đại diện cho các chuỗi được nối là một đối tượng của loại ConsString). Tất cả các cải tiến còn lại là không đáng kể (về độ phức tạp tiệm cận).
artistoex

3
Ý tưởng của bạn về cách các chuỗi được nối là sai. Để nối hai chuỗi, Javascript hoàn toàn không đọc các byte của chuỗi thành phần. Thay vào đó, nó chỉ tạo ra một đối tượng đề cập đến các phần bên trái và bên phải. Đây là lý do tại sao việc nối cuối cùng trong vòng lặp không tốn kém hơn lần đầu tiên.
artistoex

3
Tất nhiên, điều này dẫn đến chi phí lớn hơn O (1) cho việc lập chỉ mục chuỗi, do đó, việc nối có thể được làm phẳng sau đó, điều này thực sự đáng để đánh giá thêm.
artistoex

1
Đây là một bài đọc tuyệt vời. Bạn nên viết một cuốn sách về hiệu quả và tất cả những điều đó!

39

Cái này khá hiệu quả

String.prototype.repeat = function(times){
    var result="";
    var pattern=this;
    while (times > 0) {
        if (times&1)
            result+=pattern;
        times>>=1;
        pattern+=pattern;
    }
    return result;
};

11
@Olegs, tôi nghĩ rằng ý tưởng bỏ phiếu ít hơn bầu chọn cho một người hoặc cho sự sáng tạo của một người (thực sự đáng khen ngợi), nhưng ý tưởng là bỏ phiếu cho giải pháp hoàn chỉnh nhất, để có thể dễ dàng tìm thấy đứng đầu danh sách, mà không cần phải đọc tất cả các câu trả lời trong tìm kiếm cho câu trả lời hoàn hảo. (Bởi vì, thật không may, tất cả chúng ta đều có thời gian hạn chế ...)
Sorin Postelnicu

38

Tin tốt! String.prototype.repeatbây giờ là một phần của JavaScript .

"yo".repeat(2);
// returns: "yoyo"

Phương pháp này được hỗ trợ bởi tất cả các trình duyệt chính, ngoại trừ Internet Explorer và Android Webview. Để biết danh sách cập nhật, hãy xem MDN: String.prototype.repeat> Tương thích trình duyệt .

MDN có một polyfill cho các trình duyệt mà không cần hỗ trợ.


Cảm ơn báo cáo về tình trạng hiện tại, mặc dù tôi nghĩ rằng polyfill của Mozilla rất phức tạp đối với hầu hết các nhu cầu (tôi cho rằng họ cố gắng bắt chước hành vi thực thi C hiệu quả của họ) - vì vậy sẽ không thực sự trả lời yêu cầu của OP về tính đồng nhất. Bất kỳ cách tiếp cận nào khác được thiết lập dưới dạng polyfill đều bị ràng buộc ngắn gọn hơn ;-)
Guss

2
Chắc chắn rồi! Nhưng sử dụng tích hợp phải là phiên bản ngắn gọn nhất. Vì các polyfill về cơ bản chỉ là cổng sau, nên chúng sẽ hơi phức tạp để đảm bảo khả năng tương thích với thông số kỹ thuật (hoặc thông số kỹ thuật được đề xuất, trong trường hợp này). Tôi đã thêm nó cho đầy đủ, tùy thuộc vào OP để quyết định sử dụng phương pháp nào, tôi đoán vậy.
André Laszlo


17

Mở rộng giải pháp của P.Bailey :

String.prototype.repeat = function(num) {
    return new Array(isNaN(num)? 1 : ++num).join(this);
    }

Bằng cách này, bạn sẽ an toàn trước các loại đối số không mong muốn:

var foo = 'bar';
alert(foo.repeat(3));              // Will work, "barbarbar"
alert(foo.repeat('3'));            // Same as above
alert(foo.repeat(true));           // Same as foo.repeat(1)

alert(foo.repeat(0));              // This and all the following return an empty
alert(foo.repeat(false));          // string while not causing an exception
alert(foo.repeat(null));
alert(foo.repeat(undefined));
alert(foo.repeat({}));             // Object
alert(foo.repeat(function () {})); // Function

EDIT: Tín dụng để giật cho ++numý tưởng thanh lịch của mình !


2
Thay đổi một chút của bạn:String.prototype.repeat = function(n){return new Array(isNaN(n) ? 1 : ++n).join(this);}
jerone

Dù sao, theo thử nghiệm này ( jsperf.com/opes-repeat/2 ) thực hiện một vòng lặp đơn giản với chuỗi kết nối chuỗi dường như nhanh hơn trên Chrome so với sử dụng Array.join. Nó không vui sao?!
Marco Demaio

8

Sử dụng Array(N+1).join("string_to_repeat")


Tôi thích điều này. Idk tại sao nó không ở đó.
Joe Thomas

yêu nó. thật đơn giản và sạch sẽ
ekkis

5
/**  
@desc: repeat string  
@param: n - times  
@param: d - delimiter  
*/

String.prototype.repeat = function (n, d) {
    return --n ? this + (d || '') + this.repeat(n, d) : '' + this
};

đây là cách lặp lại chuỗi nhiều lần bằng cách sử dụng dấu phân cách.


4

Đây là một cải tiến 5-7% cho câu trả lời của disfated.

Hủy bỏ vòng lặp bằng cách dừng tại count > 1và thực hiện một result += pattnernconcat bổ sung sau vòng lặp. Điều này sẽ tránh các vòng lặp cuối cùng không được sử dụng pattern += patternmà không phải sử dụng một kiểm tra if đắt tiền. Kết quả cuối cùng sẽ như thế này:

String.prototype.repeat = function(count) {
    if (count < 1) return '';
    var result = '', pattern = this.valueOf();
    while (count > 1) {
        if (count & 1) result += pattern;
        count >>= 1, pattern += pattern;
    }
    result += pattern;
    return result;
};

Và đây là fiddle bị biến dạng của ngã ba cho phiên bản chưa được kiểm soát: http://jsfiddle.net/wsdfg/


2
function repeat(s, n) { var r=""; for (var a=0;a<n;a++) r+=s; return r;}

2
Không phải chuỗi kết nối tốn kém? Đó là ít nhất là trường hợp trong Java.
Vijay Dev

Tại sao họ có. Tuy nhiên, nó thực sự không thể được tối ưu hóa trong javarscript. :(
McTrafik

Điều gì về hiệu suất không phù hợp này: var r=s; for (var a=1;...:)))) Dù sao theo thử nghiệm này ( jsperf.com/opes-repeat/2 ) thực hiện một vòng lặp đơn giản với kết nối chuỗi như những gì bạn đề xuất dường như nhanh hơn trên Chrome so với sử dụng Array .tham gia.
Marco Demaio

@VijayDev - không theo thử nghiệm này: jsperf.com/ultimate-concat-vs-join
jbyrd

2

Các thử nghiệm của các phương pháp khác nhau:

var repeatMethods = {
    control: function (n,s) {
        /* all of these lines are common to all methods */
        if (n==0) return '';
        if (n==1 || isNaN(n)) return s;
        return '';
    },
    divideAndConquer:   function (n, s) {
        if (n==0) return '';
        if (n==1 || isNaN(n)) return s;
        with(Math) { return arguments.callee(floor(n/2), s)+arguments.callee(ceil(n/2), s); }
    },
    linearRecurse: function (n,s) {
        if (n==0) return '';
        if (n==1 || isNaN(n)) return s;
        return s+arguments.callee(--n, s);
    },
    newArray: function (n, s) {
        if (n==0) return '';
        if (n==1 || isNaN(n)) return s;
        return (new Array(isNaN(n) ? 1 : ++n)).join(s);
    },
    fillAndJoin: function (n, s) {
        if (n==0) return '';
        if (n==1 || isNaN(n)) return s;
        var ret = [];
        for (var i=0; i<n; i++)
            ret.push(s);
        return ret.join('');
    },
    concat: function (n,s) {
        if (n==0) return '';
        if (n==1 || isNaN(n)) return s;
        var ret = '';
        for (var i=0; i<n; i++)
            ret+=s;
        return ret;
    },
    artistoex: function (n,s) {
        var result = '';
        while (n>0) {
            if (n&1) result+=s;
            n>>=1, s+=s;
        };
        return result;
    }
};
function testNum(len, dev) {
    with(Math) { return round(len+1+dev*(random()-0.5)); }
}
function testString(len, dev) {
    return (new Array(testNum(len, dev))).join(' ');
}
var testTime = 1000,
    tests = {
        biggie: { str: { len: 25, dev: 12 }, rep: {len: 200, dev: 50 } },
        smalls: { str: { len: 5, dev: 5}, rep: { len: 5, dev: 5 } }
    };
var testCount = 0;
var winnar = null;
var inflight = 0;
for (var methodName in repeatMethods) {
    var method = repeatMethods[methodName];
    for (var testName in tests) {
        testCount++;
        var test = tests[testName];
        var testId = methodName+':'+testName;
        var result = {
            id: testId,
            testParams: test
        }
        result.count=0;

        (function (result) {
            inflight++;
            setTimeout(function () {
                result.start = +new Date();
                while ((new Date() - result.start) < testTime) {
                    method(testNum(test.rep.len, test.rep.dev), testString(test.str.len, test.str.dev));
                    result.count++;
                }
                result.end = +new Date();
                result.rate = 1000*result.count/(result.end-result.start)
                console.log(result);
                if (winnar === null || winnar.rate < result.rate) winnar = result;
                inflight--;
                if (inflight==0) {
                    console.log('The winner: ');
                    console.log(winnar);
                }
            }, (100+testTime)*testCount);
        }(result));
    }
}

2

Đây là phiên bản an toàn của JSLint

String.prototype.repeat = function (num) {
  var a = [];
  a.length = num << 0 + 1;
  return a.join(this);
};

2

Đối với tất cả các trình duyệt

Đây là về ngắn gọn như nó được:

function repeat(s, n) { return new Array(n+1).join(s); }

Nếu bạn cũng quan tâm đến hiệu suất, đây là một cách tiếp cận tốt hơn nhiều:

function repeat(s, n) { var a=[],i=0;for(;i<n;)a[i++]=s;return a.join(''); }

Nếu bạn muốn so sánh hiệu suất của cả hai tùy chọn, hãy xem FiddleFiddle này để kiểm tra điểm chuẩn. Trong các thử nghiệm của riêng tôi, tùy chọn thứ hai nhanh hơn khoảng 2 lần trong Firefox và nhanh hơn khoảng 4 lần trong Chrome!

Chỉ dành cho trình duyệt hiện đại:

Trong các trình duyệt hiện đại, bây giờ bạn cũng có thể làm điều này:

function repeat(s,n) { return s.repeat(n) };

Tùy chọn này không chỉ ngắn hơn cả hai tùy chọn khác, mà thậm chí còn nhanh hơn tùy chọn thứ hai.

Thật không may, nó không hoạt động trong bất kỳ phiên bản Internet explorer nào. Các số trong bảng chỉ định phiên bản trình duyệt đầu tiên hỗ trợ đầy đủ phương thức:

nhập mô tả hình ảnh ở đây



2

Chỉ cần một chức năng lặp lại:

function repeat(s, n) {
  var str = '';
  for (var i = 0; i < n; i++) {
    str += s;
  }
  return str;
}

2

ES2015đã được nhận ra repeat()phương pháp này !

http:
//www.ecma-i Intl.org/ecma-262/6.0/#sec-opes.prototype.repeat https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ Chuỗi / lặp lại
http://www.w3schools.com/jsref/jsref_Vpeat.asp

/** 
 * str: String
 * count: Number
 */
const str = `hello repeat!\n`, count = 3;

let resultString = str.repeat(count);

console.log(`resultString = \n${resultString}`);
/*
resultString = 
hello repeat!
hello repeat!
hello repeat!
*/

({ toString: () => 'abc', repeat: String.prototype.repeat }).repeat(2);
// 'abcabc' (repeat() is a generic method)

// Examples

'abc'.repeat(0);    // ''
'abc'.repeat(1);    // 'abc'
'abc'.repeat(2);    // 'abcabc'
'abc'.repeat(3.5);  // 'abcabcabc' (count will be converted to integer)
// 'abc'.repeat(1/0);  // RangeError
// 'abc'.repeat(-1);   // RangeError


1

Đây có thể là đệ quy nhỏ nhất: -

String.prototype.repeat = function(n,s) {
s = s || ""
if(n>0) {
   s += this
   s = this.repeat(--n,s)
}
return s}


1

Ghép đệ quy đơn giản

Tôi chỉ muốn cho nó một bash, và thực hiện điều này:

function ditto( s, r, c ) {
    return c-- ? ditto( s, r += s, c ) : r;
}

ditto( "foo", "", 128 );

Tôi không thể nói rằng tôi đã suy nghĩ nhiều, và nó có thể hiển thị :-)

Điều này được cho là tốt hơn

String.prototype.ditto = function( c ) {
    return --c ? this + this.ditto( c ) : this;
};

"foo".ditto( 128 );

Và nó rất giống như một câu trả lời đã được đăng - tôi biết điều này.

Nhưng tại sao lại được đệ quy?

Và làm thế nào về một hành vi mặc định nhỏ quá?

String.prototype.ditto = function() {
    var c = Number( arguments[ 0 ] ) || 2,
        r = this.valueOf();
    while ( --c ) {
        r += this;
    }
    return r;
}

"foo".ditto();

Bởi vì , mặc dù phương thức không đệ quy sẽ xử lý các lần lặp lớn tùy ý mà không đạt giới hạn ngăn xếp cuộc gọi, nhưng nó chậm hơn rất nhiều.

Tại sao tôi lại bận tâm thêm nhiều phương thức không thông minh bằng một nửa so với những phương pháp đã được đăng?

Một phần để giải trí cho riêng tôi, và một phần để chỉ ra một cách đơn giản nhất tôi biết rằng có nhiều cách để nuôi mèo, và tùy theo tình huống, có thể phương pháp tốt nhất rõ ràng không lý tưởng.

Một phương pháp tương đối nhanh và phức tạp có thể bị sập và cháy một cách hiệu quả trong một số trường hợp nhất định, trong khi phương pháp chậm hơn, đơn giản hơn có thể hoàn thành công việc - cuối cùng.

Một số phương pháp có thể ít hơn khai thác, và như vậy dễ bị được cố định ra khỏi sự tồn tại, và các phương pháp khác có thể làm việc đẹp trong mọi điều kiện, nhưng được xây dựng sao cho một cách đơn giản không có ý tưởng như thế nào nó hoạt động.

"Vậy nếu tôi không biết nó hoạt động thế nào?!"

Nghiêm túc?

JavaScript bị một trong những thế mạnh lớn nhất của nó; nó rất khoan dung đối với hành vi xấu và rất linh hoạt, nó sẽ cúi xuống để trả về kết quả, khi nó có thể tốt hơn cho mọi người nếu nó bị gãy!

"Với sức mạnh to lớn, có trách nhiệm lớn" ;-)

Nhưng nghiêm túc và quan trọng hơn, mặc dù những câu hỏi chung như thế này dẫn đến sự tuyệt vời dưới dạng câu trả lời thông minh mà nếu không có gì khác, hãy mở rộng kiến ​​thức và chân trời của một người, cuối cùng, nhiệm vụ trong tay - kịch bản thực tế sử dụng phương pháp kết quả - có thể yêu cầu ít hơn một chút, hoặc thông minh hơn một chút so với đề xuất.

Các thuật toán "hoàn hảo" này rất thú vị, nhưng "một kích thước phù hợp với tất cả" sẽ hiếm khi tốt hơn so với các thuật toán được thiết kế riêng.

Bài giảng này đã được mang đến cho bạn nhờ sự thiếu ngủ và sự thích thú. Đi ra và mã!


1

Đầu tiên, các câu hỏi của OP dường như là về sự đồng nhất - mà tôi hiểu là "đơn giản và dễ đọc", trong khi hầu hết các câu trả lời dường như là về hiệu quả - điều này rõ ràng không giống nhau và tôi nghĩ rằng trừ khi bạn thực hiện một số Các thuật toán thao tác dữ liệu lớn cụ thể, không nên làm bạn lo lắng khi bạn thực hiện các chức năng Javascript thao tác dữ liệu cơ bản. Sự quan tâm là quan trọng hơn nhiều.

Thứ hai, như André Laszlo đã lưu ý, String.repeat là một phần của ECMAScript 6 và đã có sẵn trong một số triển khai phổ biến - vì vậy, việc triển khai ngắn gọn nhất String.repeatlà không thực hiện nó ;-)

Cuối cùng, nếu bạn cần hỗ trợ các máy chủ không cung cấp triển khai ECMAScript 6, polyfill của MDN được đề cập bởi André Laszlo là bất cứ điều gì ngoài súc tích.

Vì vậy, không cần phải quảng cáo thêm - đây là polyfill ngắn gọn của tôi :

String.prototype.repeat = String.prototype.repeat || function(n){
    return n<=1 ? this : this.concat(this.repeat(n-1));
}

Vâng, đây là một đệ quy. Tôi thích thu hồi - chúng đơn giản và nếu được thực hiện chính xác là dễ hiểu. Về hiệu quả, nếu ngôn ngữ hỗ trợ, họ có thể rất hiệu quả nếu được viết chính xác.

Từ các thử nghiệm của tôi, phương pháp này nhanh hơn ~ 60% so với Array.joinphương pháp. Mặc dù rõ ràng là không có sự triển khai của disfated, nhưng nó đơn giản hơn nhiều so với cả hai.

Thiết lập thử nghiệm của tôi là nút v0.10, sử dụng "Chế độ nghiêm ngặt" (tôi nghĩ rằng nó cho phép một số loại TCO ), gọi repeat(1000)chuỗi 10 ký tự một triệu lần.


1

Nếu bạn nghĩ rằng tất cả các định nghĩa nguyên mẫu, sáng tạo mảng và các hoạt động tham gia là quá mức cần thiết, chỉ cần sử dụng một mã dòng duy nhất mà bạn cần nó. Chuỗi S lặp lại N lần:

for (var i = 0, result = ''; i < N; i++) result += S;

3
Mã nên được đọc. Nếu bạn thực sự chỉ sử dụng nó một lần, thì hãy định dạng đúng (hoặc sử dụng Array(N + 1).join(str)phương thức nếu đó không phải là nút cổ chai hiệu năng). Nếu có cơ hội nhỏ nhất bạn sẽ sử dụng nó hai lần, hãy chuyển nó sang một chức năng có tên thích hợp.
đám mây

1

Sử dụng Lodash cho chức năng tiện ích Javascript, như lặp lại chuỗi.

Lodash cung cấp hiệu suất tốt và khả năng tương thích ECMAScript.

Tôi đặc biệt khuyên bạn nên phát triển UI và nó cũng hoạt động tốt ở phía máy chủ.

Đây là cách lặp lại chuỗi "yo" 2 lần bằng cách sử dụng Lodash:

> _.repeat('yo', 2)
"yoyo"

0

Giải pháp đệ quy sử dụng chia và chinh phục:

function repeat(n, s) {
    if (n==0) return '';
    if (n==1 || isNaN(n)) return s;
    with(Math) { return repeat(floor(n/2), s)+repeat(ceil(n/2), s); }
}

0

Tôi đến đây một cách ngẫu nhiên và không bao giờ có lý do để lặp lại một char trong javascript trước đây.

Tôi đã bị ấn tượng bởi cách làm của nghệ sĩ và kết quả của nó. Tôi nhận thấy rằng chuỗi concat cuối cùng là không cần thiết, như Dennis cũng chỉ ra.

Tôi nhận thấy một vài điều nữa khi chơi với mẫu bị biến dạng đặt cùng nhau.

Các kết quả đã thay đổi một số tiền hợp lý thường ủng hộ lần chạy cuối cùng và các thuật toán tương tự thường chạy đua cho vị trí. Một trong những điều tôi đã thay đổi là thay vì sử dụng JSLitmus được tạo như là hạt giống cho các cuộc gọi; vì số lượng được tạo ra khác nhau cho các phương thức khác nhau, tôi đưa vào một chỉ mục. Điều này làm cho điều đáng tin cậy hơn nhiều. Sau đó tôi đã xem xét đảm bảo rằng các chuỗi có kích thước khác nhau được truyền cho các hàm. Điều này đã ngăn chặn một số biến thể tôi thấy, trong đó một số thuật toán đã làm tốt hơn ở các ký tự đơn hoặc chuỗi nhỏ hơn. Tuy nhiên, 3 phương thức hàng đầu đều làm tốt bất kể kích thước chuỗi.

Bộ kiểm tra ngã ba

http://jsfiddle.net/schmide/fCqp3/134/

// repeated string
var string = '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789';
// count paremeter is changed on every test iteration, limit it's maximum value here
var maxCount = 200;

var n = 0;
$.each(tests, function (name) {
    var fn = tests[name];
    JSLitmus.test(++n + '. ' + name, function (count) {
        var index = 0;
        while (count--) {
            fn.call(string.slice(0, index % string.length), index % maxCount);
            index++;
        }
    });
    if (fn.call('>', 10).length !== 10) $('body').prepend('<h1>Error in "' + name + '"</h1>');
});

JSLitmus.runAll();

Sau đó, tôi đã bao gồm bản sửa lỗi của Dennis và quyết định xem liệu tôi có thể tìm ra cách để khám phá thêm một chút không.

Vì javascript không thể thực sự tối ưu hóa mọi thứ, cách tốt nhất để cải thiện hiệu suất là tránh mọi thứ theo cách thủ công. Nếu tôi lấy 4 kết quả tầm thường đầu tiên ra khỏi vòng lặp, tôi có thể tránh được 2-4 cửa hàng chuỗi và viết trực tiếp cửa hàng cuối cùng vào kết quả.

// final: growing pattern + prototypejs check (count < 1)
'final avoid': function (count) {
    if (!count) return '';
    if (count == 1) return this.valueOf();
    var pattern = this.valueOf();
    if (count == 2) return pattern + pattern;
    if (count == 3) return pattern + pattern + pattern;
    var result;
    if (count & 1) result = pattern;
    else result = '';
    count >>= 1;
    do {
        pattern += pattern;
        if (count & 1) result += pattern;
        count >>= 1;
    } while (count > 1);
    return result + pattern + pattern;
}

Điều này dẫn đến sự cải thiện trung bình 1-2% so với sửa lỗi của Dennis. Tuy nhiên, các lần chạy khác nhau và các trình duyệt khác nhau sẽ cho thấy một phương sai đủ công bằng rằng mã bổ sung này có thể không xứng đáng với nỗ lực so với 2 thuật toán trước đó.

Một biểu đồ

Chỉnh sửa: Tôi đã làm điều này chủ yếu dưới chrome. Firefox và IE thường sẽ ưu tiên Dennis vài%.


0

Phương pháp đơn giản:

String.prototype.repeat = function(num) {
    num = parseInt(num);
    if (num < 0) return '';
    return new Array(num + 1).join(this);
}

0

Mọi người quá phức tạp điều này đến một mức độ vô lý hoặc hiệu suất lãng phí. Mảng nào? Đệ quy? Bạn trêu tôi.

function repeat (string, times) {
  var result = ''
  while (times-- > 0) result += string
  return result
}

Biên tập. Tôi đã chạy một số thử nghiệm đơn giản để so sánh với phiên bản bitwise được đăng bởi artistoex / disfated và một nhóm người khác. Loại thứ hai chỉ nhanh hơn một chút, nhưng hiệu quả của bộ nhớ lớn hơn. Đối với 1000000 lần lặp lại của từ 'blah', quy trình Node đã lên tới 46 megabyte với thuật toán nối đơn giản (ở trên), nhưng chỉ có 5,5 megabyte với thuật toán logarit. Sau này chắc chắn là con đường để đi. Đăng lại nó để rõ ràng:

function repeat (string, times) {
  var result = ''
  while (times > 0) {
    if (times & 1) result += string
    times >>= 1
    string += string
  }
  return result
}

Bạn có một string += stringnửa thời gian dư thừa .
nikolay

0

Chuỗi nối dựa trên một số.

function concatStr(str, num) {
   var arr = [];

   //Construct an array
   for (var i = 0; i < num; i++)
      arr[i] = str;

   //Join all elements
   str = arr.join('');

   return str;
}

console.log(concatStr("abc", 3));

Mong rằng sẽ giúp!


0

Với ES8 bạn cũng có thể sử dụng padStarthoặc padEndcho việc này. ví dụ.

var str = 'cat';
var num = 23;
var size = str.length * num;
"".padStart(size, str) // outputs: 'catcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcat'
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.