ทำซ้ำสตริง - Javascript


271

อะไรคือวิธีที่ดีที่สุดหรือรัดกุมที่สุดในการคืนค่าสตริงซ้ำจำนวนเท่าใดก็ได้?

ต่อไปนี้เป็นภาพที่ดีที่สุดของฉัน:

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

5
กว่า 10 ปีที่ผ่านมามีวิธีแก้ไขปัญหาของฉันที่รู้จักกันดีและฉันใช้เป็นตัวอย่างในบทความการเพิ่มประสิทธิภาพ JavaScript เมื่อสองสามเดือนก่อนที่คุณจะถามคำถามนี้: webreference.com/programming/javascript/jkm3/3 .htmlเห็นได้ชัดว่าคนส่วนใหญ่ลืมเกี่ยวกับรหัสนั้นและฉันไม่เห็นวิธีแก้ปัญหาใด ๆ ที่ดีด้านล่างและดีกว่าของฉัน อัลกอริทึมที่ดีที่สุดดูเหมือนว่ามันถูกยกขึ้นจากโค้ดของฉัน ยกเว้นเนื่องจากความเข้าใจผิดเกี่ยวกับวิธีการทำงานของรหัสของฉันมันเป็นขั้นตอนพิเศษหนึ่งขั้นตอนในการต่อเชื่อมแบบเอ็กโปเนนเชียลซึ่งถูกลบออกในต้นฉบับของฉันด้วยลูปพิเศษ
โจเซฟไมเออร์ส

10
ไม่มีใครยกทางออกของโจเซฟ อัลกอริทึมมีอายุ 3700 ปี ค่าใช้จ่ายของขั้นตอนพิเศษนั้นเล็กน้อย และบทความนี้มีข้อผิดพลาดและความเข้าใจผิดเกี่ยวกับการต่อสตริงใน Javascript สำหรับทุกคนที่สนใจในวิธีการ Javascript จริงๆจัดการสตริงภายในเห็นเชือก
artistoex

4
ดูเหมือนว่าจะไม่มีใครสังเกตเห็นว่ามีการกำหนดและนำสายอักขระ Protoype ซ้ำไปใช้อย่างน้อยใน Firefox
kennebec

3
@kennebec: ใช่นั่นคือฟีเจอร์ EcmaScript 6 ที่ไม่ได้อยู่รอบ ๆ เมื่อถูกถามคำถามนี้ ตอนนี้ได้รับการสนับสนุนอย่างดีพอสมควร
rvighne

3
@rvighne - ฉันเพิ่งตรวจสอบ kangax.github.io/compat-table/es6/#String.prototype.repeat ฉันจะไม่พิจารณาการสนับสนุนจาก firefox และ chrome โดยเฉพาะว่า "ได้รับการสนับสนุนเป็นอย่างดี"
aaaaaa

คำตอบ:


406

หมายเหตุถึงผู้อ่านใหม่:คำตอบนี้เก่าและไม่สามารถนำไปใช้ได้จริง - เป็นเพียง "ฉลาด" เพราะใช้ Array stuff เพื่อทำสิ่งต่างๆให้สำเร็จ เมื่อฉันเขียนว่า "กระบวนการที่น้อยลง" ฉันหมายถึง "รหัสน้อยลง" เพราะอย่างที่คนอื่น ๆ ได้กล่าวไว้ในคำตอบที่ตามมามันจะทำงานเหมือนหมู ดังนั้นอย่าใช้มันหากความเร็วสำคัญกับคุณ

ฉันจะวางฟังก์ชั่นนี้ลงในวัตถุ String โดยตรง แทนที่จะสร้างอาร์เรย์เติมและเข้าร่วมกับถ่านเปล่าเพียงแค่สร้างอาร์เรย์ที่มีความยาวที่เหมาะสมและเข้าร่วมกับสตริงที่คุณต้องการ ผลลัพธ์เดียวกันกระบวนการน้อยลง!

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

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

36
ฉันพยายามไม่ขยายวัตถุดั้งเดิม แต่อย่างอื่นนี่เป็นวิธีแก้ปัญหาที่สวยงาม ขอบคุณ!
แบรด

34
@ brad - ทำไมล่ะ คุณต้องการทำให้เนมสเปซส่วนกลางสกปรกด้วยฟังก์ชั่นที่มีรูปแบบโฮมค่อนข้างดี (วัตถุ String) ใช่ไหม
Peter Bailey

16
ที่จริงแล้วข้อโต้แย้งของคุณทั้งสองนั้นมีผลกับเนมสเปซส่วนกลางเช่นกัน ถ้าฉันจะขยายเนมสเปซและมีการชนกันฉันควรทำ 1) ไม่ใช่ในโลก 2) ในอันที่เกี่ยวข้องและ 3) ง่ายต่อการปรับโครงสร้าง นี่หมายถึงการวางไว้บนต้นแบบ String ไม่ใช่ในระดับโลก
Peter Bailey

11
การเปลี่ยนแปลงอย่างหนึ่งที่ฉันทำกับฟังก์ชั่นนี้คือการใส่ parseInt () รอบ ๆ "num" เนื่องจากถ้าคุณมีสตริงตัวเลขคุณอาจมีพฤติกรรมแปลก ๆ เนื่องจากการเล่นกลประเภท JS ตัวอย่างเช่น: "สตริงของฉัน" .repeat ("6") == "61"
nickf

19
หากคุณไม่ต้องการที่จะขยายวัตถุพื้นเมืองคุณสามารถใส่ฟังก์ชั่นบนวัตถุ String String.repeat = function(string, num){ return new Array(parseInt(num) + 1).join(string); };แทนเช่นนี้ เรียกว่าเป็นอย่างนี้:String.repeat('/\', 20)
Znarkus

204

ฉันได้ทดสอบประสิทธิภาพของวิธีการที่เสนอทั้งหมดแล้ว

นี่คือตัวแปรที่เร็วที่สุดที่ฉันมี

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;
};

หรือฟังก์ชั่นแบบสแตนด์อะโลน :

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;
}

มันขึ้นอยู่กับอัลกอริทึมartistoex มันเร็วจริงๆ และยิ่งใหญ่countเท่าไหร่ก็ยิ่งเร็วขึ้นเมื่อเทียบกับnew Array(count + 1).join(string)วิธีการ ดั้งเดิม

ฉันเปลี่ยนเพียง 2 สิ่ง:

  1. แทนที่pattern = thisด้วยpattern = this.valueOf()(ล้างการแปลงประเภทที่ชัดเจนหนึ่ง);
  2. เพิ่มการif (count < 1)ตรวจสอบจากprototypejsไปยังด้านบนของฟังก์ชันเพื่อแยกการกระทำที่ไม่จำเป็นในกรณีนั้น
  3. การเพิ่มประสิทธิภาพที่ประยุกต์ใช้จากคำตอบDennis (เพิ่มความเร็วขึ้น 5-7%)

UPD

สร้างสนามทดสอบประสิทธิภาพเล็กน้อยที่นี่สำหรับผู้ที่สนใจ

ตัวแปรcount~ 0 .. 100:

แผนภาพประสิทธิภาพ

ค่าคงที่count= 1024:

แผนภาพประสิทธิภาพ

ใช้มันและทำให้เร็วขึ้นถ้าคุณทำได้ :)


4
เยี่ยมมาก! ฉันคิดว่าcount < 1กรณีนี้คือการเพิ่มประสิทธิภาพที่ไม่จำเป็นจริงๆ
JayVee

อัลกอริทึมที่ยอดเยี่ยม O (บันทึก N) ขอบคุณสำหรับการเพิ่มประสิทธิภาพที่ยอดเยี่ยมด้วย valueOf ()
vp_arth

2
ลิงก์รูปภาพตาย
Benjamin Gruenbaum

ลิงค์ก็ใช้ได้ อาจไม่พร้อมให้บริการชั่วคราว
เลิกใช้

การทดสอบ JSFiddle ไม่ทำงานอย่างถูกต้องอีกต่อไป ดูเหมือนว่าจะเพียงแค่เรียกใช้ฟังก์ชันแรกซ้ำแล้วซ้ำอีก (ปล่อยให้มันทำงานเป็นเวลาครึ่งชั่วโมงเพื่อให้แน่ใจ)
RevanProdigalKnight

47

ปัญหานี้เป็นปัญหาการปรับให้เหมาะสมที่รู้จักกันดี / "คลาสสิก" สำหรับ JavaScript ซึ่งเกิดจากข้อเท็จจริงที่ว่าสตริง JavaScript เป็น "ไม่เปลี่ยนรูป" และนอกจากนี้โดยการเชื่อมต่อของอักขระแม้แต่ตัวเดียวกับสตริงที่ต้องการสร้างรวมถึงการจัดสรรหน่วยความจำ สตริงใหม่ทั้งหมด

น่าเสียดายที่คำตอบที่ยอมรับในหน้านี้ผิดที่ "ผิด" หมายถึงปัจจัยประสิทธิภาพ 3x สำหรับสายอักขระตัวเดียวอย่างง่ายและ 8x-97x สำหรับสตริงสั้น ๆ ซ้ำหลายครั้งเป็น 300x สำหรับการทำซ้ำประโยคและผิดอย่างไม่สิ้นสุดเมื่อ การ จำกัด อัตราส่วนของความซับซ้อนของอัลกอริธึมที่nจะไม่มีที่สิ้นสุด นอกจากนี้ยังมีคำตอบในหน้านี้อีกซึ่งเกือบจะถูก (ขึ้นอยู่กับหนึ่งในหลาย ๆ รุ่นและรูปแบบของการแก้ปัญหาที่ถูกต้องซึ่งแพร่กระจายไปทั่วอินเทอร์เน็ตใน 13 ปีที่ผ่านมา) อย่างไรก็ตามโซลูชัน "เกือบถูกต้อง" นี้พลาดจุดสำคัญของอัลกอริธึมที่ถูกต้องทำให้ประสิทธิภาพลดลง 50%

JS Performance Results สำหรับคำตอบที่ได้รับการยอมรับคำตอบอื่น ๆ ที่มีประสิทธิภาพสูงสุด (ขึ้นอยู่กับเวอร์ชั่นดั้งเดิมของอัลกอริทึมดั้งเดิมในคำตอบนี้) และคำตอบนี้ใช้อัลกอริทึมของฉันสร้างขึ้นเมื่อ 13 ปีที่แล้ว

~ ตุลาคม 2000 ฉันเผยแพร่อัลกอริทึมสำหรับปัญหาที่แน่นอนซึ่งได้รับการดัดแปลงดัดแปลงอย่างกว้างขวางจากนั้นในที่สุดก็เข้าใจและลืมได้ไม่ดี เพื่อแก้ไขปัญหานี้ในเดือนสิงหาคม 2008 ฉันเผยแพร่บทความhttp://www.webreference.com/programming/javascript/jkm3/3.htmlอธิบายอัลกอริทึมและใช้เป็นตัวอย่างของการเพิ่มประสิทธิภาพ JavaScript ทั่วไปอย่างง่าย ในตอนนี้การอ้างอิงทางเว็บได้ขัดข้อมูลการติดต่อของฉันและแม้แต่ชื่อของฉันจากบทความนี้ และอีกครั้งที่อัลกอริทึมได้รับการดัดแปลงดัดแปลงอย่างกว้างขวางจากนั้นก็เข้าใจไม่ดีและลืมไปมาก

สตริงอัลกอริทึมการซ้ำซ้อนซ้ำสตริงเดิมโดย Joseph Myers ประมาณ Y2K เป็นฟังก์ชันการคูณข้อความภายใน Text.js เผยแพร่เมื่อสิงหาคม 2551 ในรูปแบบนี้โดยการอ้างอิงทางเว็บ: http://www.webreference.com/programming/javascript/jkm3/3.html (บทความนี้ใช้ฟังก์ชั่นเป็นตัวอย่างของการเพิ่มประสิทธิภาพ JavaScript ซึ่งเป็นสิ่งเดียวที่แปลก ชื่อ "stringFill3.")

/*
 * 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;
}

ภายในสองเดือนหลังจากการตีพิมพ์บทความนั้นคำถามเดียวกันนี้ถูกโพสต์ไว้ใน Stack Overflow และบินไปใต้เรดาร์ของฉันจนถึงตอนนี้เมื่อเห็นได้ชัดว่าอัลกอริทึมดั้งเดิมสำหรับปัญหานี้ถูกลืมอีกครั้ง ทางออกที่ดีที่สุดที่มีอยู่ในหน้า Stack Overflow คือโซลูชันของฉันที่ได้รับการแก้ไขซึ่งอาจแยกจากกันหลายชั่วอายุคน น่าเสียดายที่การปรับเปลี่ยนนั้นทำให้ประสิทธิภาพการทำงานของโซลูชันดีขึ้น อันที่จริงแล้วโดยการเปลี่ยนโครงสร้างของลูปจากต้นฉบับของฉันโซลูชันที่แก้ไขจะดำเนินการขั้นตอนพิเศษที่ไม่จำเป็นอย่างสมบูรณ์ของการทำซ้ำเลขชี้กำลัง (ดังนั้นการรวมสตริงที่ใหญ่ที่สุดที่ใช้ในคำตอบที่เหมาะสม

ด้านล่างนี้เป็นการสนทนาของการเพิ่มประสิทธิภาพ JavaScript บางอย่างที่เกี่ยวข้องกับคำตอบทั้งหมดของปัญหานี้และเพื่อประโยชน์ของทุกคน

เทคนิค: หลีกเลี่ยงการอ้างอิงถึงวัตถุหรือคุณสมบัติของวัตถุ

เพื่อแสดงให้เห็นว่าเทคนิคนี้ทำงานอย่างไรเราใช้ฟังก์ชัน JavaScript ในชีวิตจริงซึ่งสร้างสตริงของความยาวเท่าใดก็ได้ตามต้องการ และอย่างที่เราเห็นจะสามารถเพิ่มประสิทธิภาพได้มากขึ้น!

ฟังก์ชั่นเช่นเดียวกับที่ใช้ในที่นี้คือการสร้างการเติมเพื่อจัดเรียงคอลัมน์ของข้อความสำหรับการจัดรูปแบบเงินหรือสำหรับการกรอกข้อมูลบล็อกถึงขอบเขต ฟังก์ชั่นการสร้างข้อความยังอนุญาตให้มีการป้อนความยาวผันแปรสำหรับการทดสอบฟังก์ชั่นอื่น ๆ ที่ทำงานกับข้อความ ฟังก์ชั่นนี้เป็นหนึ่งในองค์ประกอบที่สำคัญของโมดูลประมวลผลข้อความ JavaScript

เมื่อเราดำเนินการต่อไปเราจะกล่าวถึงเทคนิคการเพิ่มประสิทธิภาพที่สำคัญที่สุดอีกสองประการในขณะที่การพัฒนารหัสต้นฉบับให้เป็นอัลกอริธึมที่เหมาะสมที่สุดสำหรับการสร้างสตริง ผลลัพธ์สุดท้ายคือฟังก์ชั่นความแข็งแรงของอุตสาหกรรมและประสิทธิภาพสูงที่ฉันใช้ไปทุกหนทุกแห่ง - จัดเรียงราคาสินค้าและผลรวมในรูปแบบคำสั่ง JavaScript การจัดรูปแบบข้อมูลและการจัดรูปแบบอีเมล / ข้อความและการใช้งานอื่น ๆ อีกมากมาย

รหัสดั้งเดิมสำหรับการสร้างสตริง stringFill1()

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

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

โปรดทราบว่ามีการอ้างอิงผู้บริสุทธิ์หนึ่งครั้งไปยังคุณสมบัติวัตถุs.lengthในรหัสที่ทำให้ประสิทธิภาพการทำงานแย่ลง ยิ่งแย่ไปกว่าการใช้คุณสมบัติวัตถุนี้ลดความเรียบง่ายของโปรแกรมโดยทำให้ผู้อ่านรู้เกี่ยวกับคุณสมบัติของวัตถุสตริง JavaScript

การใช้คุณสมบัติวัตถุนี้ทำลายความเป็นสากลของโปรแกรมคอมพิวเตอร์ โปรแกรมถือว่าxเป็นสตริงที่มีความยาวหนึ่งรายการ สิ่งนี้ จำกัด แอปพลิเคชันของstringFill1()ฟังก์ชั่นเป็นอะไรก็ได้ยกเว้นการทำซ้ำอักขระเดี่ยว &nbsp;แม้แต่ตัวอักษรเดียวไม่สามารถใช้ในกรณีที่พวกเขามีไบต์หลายเช่นนิติบุคคลของ HTML

xปัญหาที่เลวร้ายที่สุดที่เกิดจากการใช้งานที่ไม่จำเป็นของสถานที่ให้บริการวัตถุก็คือฟังก์ชั่นสร้างห่วงอนันต์ถ้าการทดสอบบนสายป้อนที่ว่างเปล่า ในการตรวจสอบทั่วไปให้ใช้โปรแกรมกับปริมาณที่น้อยที่สุดที่เป็นไปได้ โปรแกรมที่เกิดปัญหาเมื่อถูกขอให้เกินจำนวนหน่วยความจำที่มีอยู่จะมีข้อแก้ตัว โปรแกรมเช่นนี้ซึ่งล้มเหลวเมื่อถูกขอให้สร้างอะไรที่ไม่สามารถยอมรับได้ บางครั้งรหัสสวยเป็นรหัสพิษ

ความเรียบง่ายอาจเป็นเป้าหมายที่ชัดเจนของการเขียนโปรแกรมคอมพิวเตอร์ แต่โดยทั่วไปแล้วไม่ใช่ เมื่อโปรแกรมขาดระดับความรู้ทั่วไปพอสมควรไม่สามารถพูดได้ว่า "โปรแกรมนั้นดีพอที่จะไป" อย่างที่คุณเห็นการใช้string.lengthคุณสมบัติป้องกันโปรแกรมนี้ไม่ให้ทำงานในการตั้งค่าทั่วไปและในความเป็นจริงโปรแกรมที่ไม่ถูกต้องพร้อมที่จะทำให้เบราว์เซอร์หรือระบบล่ม

มีวิธีในการปรับปรุงประสิทธิภาพของ JavaScript นี้รวมทั้งดูแลปัญหาร้ายแรงทั้งสองนี้หรือไม่

แน่นอน. เพียงใช้จำนวนเต็ม

รหัสที่ปรับให้เหมาะสมสำหรับการสร้างสตริง stringFill2()

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

รหัสเวลาเพื่อเปรียบเทียบ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); 

ความสำเร็จจนถึงตอนนี้ stringFill2()

stringFill1()ใช้เวลา 47.297 ไมโครวินาที (หนึ่งในล้านของวินาที) เพื่อเติมสตริง 100 ไบต์และstringFill2()ใช้เวลา 27.68 ไมโครวินาทีในการทำสิ่งเดียวกัน นั่นเกือบเป็นสองเท่าในประสิทธิภาพโดยการหลีกเลี่ยงการอ้างอิงถึงคุณสมบัติของวัตถุ

เทคนิค: หลีกเลี่ยงการเพิ่มสายสั้นลงในสายยาว

ผลลัพธ์ก่อนหน้าของเราดูดี - ดีมากในความเป็นจริง ฟังก์ชั่นที่ได้รับการปรับปรุงstringFill2()นั้นเร็วขึ้นมากเนื่องจากการใช้การเพิ่มประสิทธิภาพสองครั้งแรกของเรา คุณจะเชื่อไหมถ้าฉันบอกคุณว่ามันสามารถปรับปรุงให้เร็วขึ้นกว่าเดิมหลายเท่า?

ใช่เราสามารถบรรลุเป้าหมายนั้นได้ ตอนนี้เราต้องอธิบายวิธีที่เราหลีกเลี่ยงการต่อท้ายสายสั้น ๆ กับสายยาว

พฤติกรรมระยะสั้นดูเหมือนจะค่อนข้างดีเมื่อเทียบกับฟังก์ชั่นดั้งเดิมของเรา นักวิทยาศาสตร์คอมพิวเตอร์ต้องการวิเคราะห์ "พฤติกรรมแบบอะซิมโทติค" ของฟังก์ชันหรืออัลกอริทึมของโปรแกรมคอมพิวเตอร์ซึ่งหมายถึงการศึกษาพฤติกรรมระยะยาวโดยการทดสอบด้วยอินพุตที่มีขนาดใหญ่ขึ้น บางครั้งหากไม่ทำการทดสอบเพิ่มเติมจะไม่มีใครรู้ว่าจะปรับปรุงโปรแกรมคอมพิวเตอร์อย่างไร เพื่อดูว่าจะเกิดอะไรขึ้นเราจะสร้างสตริงขนาด 200 ไบต์

ปัญหาที่เกิดขึ้นกับ stringFill2()

จากการใช้ฟังก์ชั่นจับเวลาของเราเราพบว่าเวลาเพิ่มขึ้นเป็น 62.54 microseconds สำหรับสตริง 200 ไบต์เปรียบเทียบกับ 27.68 สำหรับสตริง 100 ไบต์ ดูเหมือนว่าเวลาควรเพิ่มเป็นสองเท่าสำหรับการทำงานสองเท่า แต่แทนที่จะเป็นสามเท่าหรือสี่เท่า จากประสบการณ์การเขียนโปรแกรมผลลัพธ์นี้ดูแปลกเพราะถ้ามีสิ่งใดฟังก์ชั่นควรจะเร็วขึ้นเล็กน้อยเนื่องจากงานเสร็จสิ้นอย่างมีประสิทธิภาพ (200 ไบต์ต่อการเรียกใช้ฟังก์ชันมากกว่า 100 ไบต์ต่อการเรียกใช้ฟังก์ชัน) ปัญหานี้เกี่ยวกับคุณสมบัติที่ร้ายกาจของสตริง JavaScript: สตริง JavaScript นั้น "ไม่เปลี่ยนรูป"

หมายความว่าไม่เปลี่ยนรูปซึ่งคุณไม่สามารถเปลี่ยนสตริงได้เมื่อมันถูกสร้างขึ้น โดยการเพิ่มทีละหนึ่งไบต์เราจะไม่ใช้ความพยายามอีกหนึ่งไบต์ เรากำลังสร้างสตริงทั้งหมดขึ้นใหม่บวกกับอีกหนึ่งไบต์

ผลในการเพิ่มหนึ่งไบต์เพิ่มเติมในสตริง 100 ไบต์จะใช้งานมูลค่า 101 ไบต์ มาวิเคราะห์ค่าใช้จ่ายในการคำนวณสั้น ๆ เพื่อสร้างสตริงของNไบต์ ต้นทุนของการเพิ่มไบต์แรกคือ 1 หน่วยของความพยายามในการคำนวณ ค่าใช้จ่ายในการเพิ่มไบต์ที่สองไม่ใช่หน่วยเดียว แต่มี 2 หน่วย (การคัดลอกไบต์แรกไปยังวัตถุสตริงใหม่รวมถึงการเพิ่มไบต์ที่สอง) ไบต์ที่สามต้องการค่าใช้จ่าย 3 หน่วยเป็นต้น

C(N) = 1 + 2 + 3 + ... + N = N(N+1)/2 = O(N^2). สัญลักษณ์O(N^2)เด่นชัดคือบิ๊กโอของ N กำลังสองและหมายความว่าต้นทุนการคำนวณในระยะยาวนั้นเป็นสัดส่วนกับกำลังสองของความยาวสตริง ในการสร้างตัวละคร 100 ตัวใช้เวลาทำงาน 10,000 หน่วยและเพื่อสร้างตัวละคร 100 ตัวต้องใช้งาน 40,000 หน่วย

นี่คือสาเหตุที่ใช้เวลานานกว่าสองเท่าในการสร้าง 200 อักขระมากกว่า 100 ตัวอักษร ในความเป็นจริงมันควรใช้เวลานานสี่เท่า ประสบการณ์การเขียนโปรแกรมของเรานั้นถูกต้องในการทำงานที่มีประสิทธิภาพมากขึ้นเล็กน้อยสำหรับสายยาวและดังนั้นจึงใช้เวลาเพียงประมาณสามครั้ง เมื่อค่าใช้จ่ายในการเรียกใช้ฟังก์ชันน้อยมากเมื่อพิจารณาจากจำนวนสตริงที่เรากำลังสร้างมันจะใช้เวลานานกว่าการสร้างสตริงเป็นสองเท่า

(หมายเหตุประวัติ: การวิเคราะห์นี้ไม่จำเป็นต้องใช้กับสตริงในซอร์สโค้ดเช่นhtml = 'abcd\n' + 'efgh\n' + ... + 'xyz.\n'เนื่องจากคอมไพเลอร์ซอร์สโค้ด JavaScript สามารถรวมสตริงเข้าด้วยกันก่อนทำให้เป็นวัตถุสตริง JavaScript เพียงไม่กี่ปีที่ผ่านมาการใช้ KJS ของ JavaScript จะหยุดหรือผิดพลาดเมื่อโหลดซอร์สโค้ดที่ยาวเข้าร่วมด้วยเครื่องหมายบวกเนื่องจากเวลาในการคำนวณเป็นO(N^2)เรื่องยากที่จะสร้างเว็บเพจที่ใช้งานเว็บเบราเซอร์ Konqueror หรือ Safari มากเกินไปซึ่งใช้ KJS JavaScript core engine ก่อน เจอปัญหานี้เมื่อฉันพัฒนาภาษามาร์กอัปและตัวแยกวิเคราะห์ภาษามาร์กอัป JavaScript แล้วฉันค้นพบสิ่งที่ทำให้เกิดปัญหาเมื่อฉันเขียนสคริปต์ของฉันสำหรับ JavaScript รวม.)

เห็นได้ชัดว่าการเสื่อมประสิทธิภาพอย่างรวดเร็วนี้เป็นปัญหาใหญ่ เราจะจัดการกับมันอย่างไรเนื่องจากเราไม่สามารถเปลี่ยนวิธีจัดการสตริงของ JavaScript เป็นวัตถุที่ไม่เปลี่ยนรูปแบบได้? วิธีแก้ไขคือใช้อัลกอริธึมที่สร้างสตริงขึ้นใหม่สองสามครั้งเท่าที่จะทำได้

เพื่อให้ชัดเจนยิ่งขึ้นเป้าหมายของเราคือการหลีกเลี่ยงการเพิ่มสตริงสั้น ๆ ให้กับสตริงที่มีความยาวเนื่องจากในการเพิ่มสตริงสั้น ๆ นั้นสตริงที่มีความยาวทั้งหมดจะต้องซ้ำกัน

อัลกอริทึมทำงานอย่างไรเพื่อหลีกเลี่ยงการเพิ่มสตริงสั้น ๆ ให้กับสตริงยาว

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

ตัวอย่างเช่นการสร้างสตริงที่มีความยาว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 */

การทำเช่นนี้จำเป็นต้องสร้างสตริงที่มีความยาว 1 สร้างสตริงที่มีความยาว 2 สร้างสตริงที่มีความยาว 4 สร้างสตริงที่มีความยาว 8 และสุดท้ายสร้างสตริงที่มีความยาว 9 เราประหยัดค่าใช้จ่ายได้เท่าไร

C(9) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 9 = 45ต้นทุนเก่า

C(9) = 1 + 2 + 4 + 8 + 9 = 24ค่าใช้จ่ายใหม่

โปรดทราบว่าเราต้องเพิ่มสตริงที่มีความยาว 1 ไปยังสตริงที่มีความยาว 0 จากนั้นสตริงที่มีความยาว 1 ถึงสตริงที่มีความยาว 1 จากนั้นสตริงที่มีความยาว 2 ถึงสตริงที่มีความยาว 2 จากนั้นสตริงที่มีความยาว 4 กับสตริงที่มีความยาว 4 จากนั้นสตริงที่มีความยาว 8 ถึงสตริงที่มีความยาว 1 เพื่อรับสตริงที่มีความยาว 9 สิ่งที่เรากำลังทำอยู่สามารถสรุปได้ว่าหลีกเลี่ยงการเพิ่มสตริงสั้นลงในสตริงยาวหรืออื่น ๆ คำพยายามเชื่อมสตริงเข้าด้วยกันที่มีความยาวเท่ากันหรือเกือบเท่ากัน

N(N+1)/2สำหรับค่าใช้จ่ายในการคำนวณเก่าที่เราพบสูตร มีสูตรสำหรับค่าใช้จ่ายใหม่หรือไม่? ใช่ แต่มันซับซ้อน สิ่งที่สำคัญคือมันเป็นO(N)และดังนั้นความยาวของสตริงจะเพิ่มขึ้นเป็นสองเท่าของปริมาณงานแทนที่จะเป็นสี่เท่า

รหัสที่ใช้ความคิดใหม่นี้มีความซับซ้อนเกือบเท่ากับสูตรสำหรับต้นทุนการคำนวณ เมื่อคุณอ่านให้จำไว้ว่า>>= 1หมายถึงการเลื่อนไปทางขวา 1 ไบต์ ดังนั้นถ้าn = 10011เป็นเลขฐานสองแล้วส่งผลให้เกิดความคุ้มค่าn >>= 1n = 1001

ส่วนอื่น ๆ &ของรหัสที่คุณอาจไม่รู้จักเป็นค่าที่เหมาะสมและผู้ประกอบการเขียน นิพจน์n & 1ประเมินค่าจริงถ้าเลขฐานสองตัวสุดท้ายของnเป็น 1 และเท็จถ้าเลขฐานสองตัวสุดท้ายของnเป็น 0

stringFill3()ฟังก์ชั่นใหม่ที่มีประสิทธิภาพสูง

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

มันดูน่าเกลียดสำหรับดวงตาที่ไม่ได้รับการฝึกฝน แต่การแสดงมันไม่มีอะไรที่น่ารักไปกว่านี้อีกแล้ว

มาดูกันว่าฟังก์ชั่นนี้ทำงานได้ดีแค่ไหน หลังจากเห็นผลลัพธ์เป็นไปได้ว่าคุณจะไม่มีวันลืมความแตกต่างระหว่างO(N^2)อัลกอริทึมกับO(N)อัลกอริทึม

stringFill1()ใช้เวลา 88.7 microseconds (หนึ่งในล้านของวินาที) เพื่อสร้างสตริง 200 ไบต์stringFill2()ใช้เวลา 62.54 และstringFill3()ใช้เวลาเพียง 4.608 อะไรที่ทำให้อัลกอริทึมนี้ดีขึ้นมาก? ทุกฟังก์ชั่นใช้ประโยชน์จากฟังก์ชั่นการใช้ตัวแปรท้องถิ่น stringFill3()แต่การใช้ประโยชน์จากเทคนิคการเพิ่มประสิทธิภาพที่สองและสามที่เพิ่มการปรับปรุงยี่สิบเท่าเพื่อประสิทธิภาพการทำงานของ

การวิเคราะห์เชิงลึก

อะไรทำให้ฟังก์ชั่นนี้ทำให้การแข่งขันตกจากน้ำ?

ดังที่ฉันได้กล่าวถึงเหตุผลที่ทั้งสองฟังก์ชั่นเหล่านี้stringFill1()และstringFill2()ทำงานช้ามากคือสตริง JavaScript นั้นไม่เปลี่ยนรูป ไม่สามารถจัดสรรหน่วยความจำใหม่เพื่ออนุญาตให้เพิ่มหนึ่งไบต์ทีละครั้งเพื่อผนวกเข้ากับข้อมูลสตริงที่จัดเก็บโดย JavaScript ทุกครั้งที่มีการเพิ่มหนึ่งไบต์ในตอนท้ายของสตริงสตริงทั้งหมดจะถูกสร้างใหม่ตั้งแต่ต้นจนจบ

ดังนั้นเพื่อปรับปรุงประสิทธิภาพของสคริปต์เราจะต้องคำนวณสตริงความยาวให้ยาวขึ้นโดยการต่อสองสตริงเข้าด้วยกันก่อนแล้วจึงสร้างความยาวสตริงที่ต้องการซ้ำ

ตัวอย่างเช่นในการสร้างสตริงไบต์ 16 ตัวอักษรแรกสตริงสองไบต์จะถูกคำนวณไว้ล่วงหน้า จากนั้นสตริงสองไบต์จะถูกนำมาใช้ซ้ำเพื่อคำนวณสตริงสี่ไบต์ล่วงหน้าล่วงหน้า จากนั้นสตริงสี่ไบต์จะถูกนำมาใช้ซ้ำเพื่อคำนวณสตริงแปดไบต์ล่วงหน้า ในที่สุดสตริงแปดไบต์สองเส้นจะถูกนำมาใช้ซ้ำเพื่อสร้างสตริงใหม่ที่ต้องการขนาด 16 ไบต์ จะต้องสร้างสตริงใหม่สี่สายโดยมีหนึ่งความยาว 2 หนึ่งในความยาว 4 หนึ่งในความยาว 8 และหนึ่งในความยาว 16 ค่าใช้จ่ายทั้งหมดคือ 2 + 4 + 8 + 16 = 30

ในระยะยาวประสิทธิภาพนี้สามารถคำนวณได้โดยการเพิ่มในลำดับย้อนกลับและการใช้ชุดเรขาคณิตเริ่มต้นด้วยคำแรก a1 = N และมีอัตราส่วนทั่วไปของ r = 1/2 a_1 / (1-r) = 2Nผลรวมของชุดเรขาคณิตจะได้รับจาก

สิ่งนี้มีประสิทธิภาพมากกว่าการเพิ่มอักขระหนึ่งตัวเพื่อสร้างสตริงความยาวใหม่ 2 สร้างสตริงใหม่ที่มีความยาว 3, 4, 5 และต่อ ๆ ไปจนกระทั่งถึง 16 อัลกอริทึมก่อนหน้านี้ใช้กระบวนการนั้นเพิ่มไบต์เดียวในแต่ละครั้ง n (n + 1) / 2 = 16 (17) / 2 = 8 (17) = 136และค่าใช้จ่ายทั้งหมดของมันจะเป็น

เห็นได้ชัดว่า 136 เป็นจำนวนที่มากกว่า 30 ดังนั้นอัลกอริทึมก่อนหน้านี้ใช้เวลานานกว่ามากในการสร้างสตริง

ในการเปรียบเทียบทั้งสองวิธีคุณสามารถดูว่าอัลกอริทึมแบบเรียกซ้ำ (เรียกอีกอย่างว่า "การหารและการพิชิต") เร็วขึ้นเท่าใดบนสตริงที่มีความยาว 123,457 ในคอมพิวเตอร์ FreeBSD ของฉันอัลกอริทึมนี้นำมาใช้ในstringFill3()ฟังก์ชั่นสร้างสตริงใน 0.001058 วินาทีในขณะที่stringFill1()ฟังก์ชั่นดั้งเดิมสร้างสตริงใน 0.0808 วินาที ฟังก์ชั่นใหม่เร็วกว่า 76 เท่า

ความแตกต่างของประสิทธิภาพเพิ่มขึ้นเมื่อความยาวของสตริงใหญ่ขึ้น ในวงเงินที่เป็นสตริงที่มีขนาดใหญ่และมีขนาดใหญ่มีการสร้างฟังก์ชั่นเดิมทำงานคร่าว ๆ เช่นC1(คงที่) ครั้งN^2และพฤติกรรมของฟังก์ชั่นใหม่ ๆ เช่นC2(คงที่) Nครั้ง

จากการทดลองของเราที่เราสามารถกำหนดค่าของการC1ที่จะเป็นC1 = 0.0808 / (123457)2 = .00000000000530126997และความคุ้มค่าของการที่จะเป็นC2 C2 = 0.001058 / 123457 = .00000000856978543136ใน 10 วินาทีฟังก์ชั่นใหม่สามารถสร้างสตริงที่มี 1,166,890,359 ตัวอักษร เพื่อสร้างสตริงเดียวกันนี้ฟังก์ชั่นเก่าจะต้องใช้เวลา 7,218,384 วินาที

นี่คือเกือบสามเดือนเมื่อเทียบกับสิบวินาที!

ฉันตอบเพียงแค่นี้ (หลายปีที่ผ่านมา) เพราะวิธีแก้ปัญหาดั้งเดิมของฉันนี้ลอยอยู่ในอินเทอร์เน็ตมานานกว่า 10 ปีและเห็นได้ชัดว่ายังมีคนไม่กี่คนที่จำได้ยาก ฉันคิดว่าโดยการเขียนบทความเกี่ยวกับที่นี่ฉันจะช่วย:

การปรับปรุงประสิทธิภาพสำหรับ JavaScript ความเร็วสูง / หน้า 3

น่าเสียดายที่โซลูชันอื่น ๆ ที่นำเสนอที่นี่ยังคงเป็นโซลูชันที่ใช้เวลาสามเดือนในการสร้างผลลัพธ์ในปริมาณเดียวกันกับที่โซลูชันที่เหมาะสมสร้างขึ้นใน 10 วินาที

ฉันต้องการใช้เวลาในการทำซ้ำบางส่วนของบทความที่นี่เป็นคำตอบที่ยอมรับได้ใน Stack Overflow

โปรดทราบว่าอัลกอริทึมที่มีประสิทธิภาพดีที่สุดที่นี่จะขึ้นอยู่กับอัลกอริทึมของฉันอย่างชัดเจนและอาจสืบทอดมาจากการปรับรุ่นที่ 3 หรือ 4 ของคนอื่น น่าเสียดายที่การปรับเปลี่ยนนี้ส่งผลให้ประสิทธิภาพลดลง การแปรผันของโซลูชันของฉันที่นำเสนอในที่นี้อาจไม่เข้าใจfor (;;)นิพจน์ที่สับสนของฉันซึ่งดูเหมือนลูปหลักที่ไม่มีที่สิ้นสุดของเซิร์ฟเวอร์ที่เขียนใน C และได้รับการออกแบบอย่างเรียบง่ายเพื่อให้คำสั่ง break แบ่งตำแหน่งอย่างระมัดระวังสำหรับการควบคุมลูป หลีกเลี่ยงการทำซ้ำสตริงอย่างไม่จำเป็นชี้แจงหนึ่งครั้งโดยไม่จำเป็น


4
คำตอบนี้ไม่ควรได้รับ upvotes มากมาย ประการแรกการอ้างสิทธิ์โดยโจเซฟเป็นเรื่องน่าขัน อัลกอริทึมพื้นฐานมีอายุ 3700 ปี
artistoex

2
ประการที่สองมันมีข้อมูลที่ผิดจำนวนมาก การใช้งาน Javascript สมัยใหม่ไม่แม้แต่จะสัมผัสเนื้อหาของสตริงเมื่อทำการเชื่อมต่อ (v8 หมายถึงสตริงที่ต่อกันเป็นวัตถุประเภท ConsString) การปรับปรุงที่เหลือทั้งหมดนั้นเล็กน้อย (ในแง่ของความซับซ้อนเชิงซีมโทติค)
artistoex

3
ความคิดของคุณเกี่ยวกับการเรียงสตริงนั้นผิดอย่างไร ในการเชื่อมสองสายเข้าด้วยกัน Javascript จะไม่อ่านไบต์ของสตริงที่เป็นส่วนประกอบเลย แต่มันสร้างเพียงวัตถุที่อ้างถึงส่วนซ้ายและขวา นี่คือเหตุผลที่การต่อข้อมูลล่าสุดในลูปไม่ได้มีราคาแพงกว่าการเชื่อมต่อครั้งแรก
artistoex

3
แน่นอนว่าสิ่งนี้ส่งผลให้มีค่าใช้จ่ายสูงกว่า O (1) สำหรับการจัดทำดัชนีสตริงดังนั้นการต่อข้อมูลอาจถูกทำให้แบนออกในภายหลังซึ่งแน่นอนว่าเป็นการประเมินเพิ่มเติม
artistoex

1
นี่เป็นการอ่านที่ยอดเยี่ยม คุณควรเขียนหนังสือเกี่ยวกับประสิทธิภาพและสิ่งนั้น!

39

อันนี้ค่อนข้างมีประสิทธิภาพ

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
@Ologs ฉันคิดว่าความคิดของการลงคะแนนน้อยกว่าการลงคะแนนให้กับบุคคลหรือความคิดสร้างสรรค์ของบุคคล (ซึ่งแน่นอนว่าน่าชื่นชม) แต่ความคิดคือการลงคะแนนให้โซลูชั่นที่สมบูรณ์ที่สุดเพื่อให้สามารถหาได้ง่ายที่ ด้านบนของรายการโดยไม่ต้องอ่านคำตอบทั้งหมดในการค้นหาคำตอบที่สมบูรณ์แบบ (เพราะน่าเสียดายที่เราทุกคนมีเวลา จำกัด ... )
โซริน Postelnicu

38

ข่าวดี! String.prototype.repeatเป็นส่วนหนึ่งของจาวาสคริปต์

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

เบราว์เซอร์หลักทั้งหมดรองรับวิธีนี้ยกเว้น Internet Explorer และ Android Webview ได้ถึงวันที่รายการให้ดูMDN: String.prototype.repeat> เข้ากันได้ของเบราว์เซอร์

MDN มีpolyfillสำหรับเบราว์เซอร์ที่ไม่มีการสนับสนุน


ขอบคุณที่รายงานเกี่ยวกับสถานการณ์ปัจจุบันแม้ว่าฉันคิดว่า Mozilla polyfill นั้นซับซ้อนสำหรับความต้องการส่วนใหญ่ (ฉันคิดว่าพวกเขาพยายามเลียนแบบพฤติกรรมการใช้ C ที่มีประสิทธิภาพ) - ดังนั้นจะไม่ตอบสนองความต้องการของ OP สำหรับความรัดกุม ใด ๆ ของวิธีการอื่น ๆ ที่กำหนดขึ้นเป็น polyfill จะผูกพันที่จะกระชับมากขึ้น ;-)
Guss

2
อย่างแน่นอน! แต่การใช้งานบิวท์อินจะต้องเป็นเวอร์ชั่นที่สั้นที่สุด เนื่องจากโพลีฟิลล์เป็นเพียงแบ็คพอร์ตพวกมันจึงค่อนข้างซับซ้อนเพื่อให้เข้ากันได้กับสเปค (หรือสเปคที่เสนอในกรณีนี้) ฉันเพิ่มมันเพื่อความสมบูรณ์มันขึ้นอยู่กับ OP เพื่อตัดสินใจว่าจะใช้วิธีใดฉันเดา
André Laszlo

19

String.prototype.repeatตอนนี้เป็นมาตรฐาน ES6

'abc'.repeat(3); //abcabcabc

ดี! .. แต่ไม่สามารถใช้งานได้สำหรับฉัน (ไม่รองรับใน iOS <9): kangax.github.io/compat-table/es6
Benjamin

@Benjamin ใช้ polyfill บนลิงค์
ลูอิส

ฉันคิดว่าควรเป็นคำตอบที่ตรึงไว้
test30

17

การขยายโซลูชันของ P.Bailey :

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

วิธีนี้คุณควรปลอดภัยจากประเภทอาร์กิวเมนต์ที่ไม่คาดคิด:

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

แก้ไข: ให้เครดิตjeroneสำหรับ++numความคิดที่หรูหราของเขา!


2
การเปลี่ยนแปลงของคุณเพียงเล็กน้อย:String.prototype.repeat = function(n){return new Array(isNaN(n) ? 1 : ++n).join(this);}
jerone

อย่างไรก็ตามจากการทดสอบนี้ ( jsperf.com/string-repeat/2 ) การทำลูปด้วยการเชื่อมสตริงนั้นดูเหมือนว่าจะเร็วกว่า Chrome เมื่อเทียบกับการใช้ Array.join ไม่ตลกเหรอ!
Marco Demaio

8

ใช้ Array(N+1).join("string_to_repeat")


ฉันชอบสิ่งนี้. idk ทำไมมันไม่ได้อยู่ที่นั่น
Joe Thomas

รักมัน เรียบง่ายและสะอาดมาก
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
};

นี่คือวิธีการทำซ้ำสตริงหลาย ๆ ครั้งโดยใช้ delimeter


4

นี่คือการปรับปรุง 5-7% สำหรับคำตอบของ disfated

ปลดลูปโดยการหยุดcount > 1และทำการเชื่อมต่อเพิ่มเติมresult += pattnernหลังจากลูป สิ่งนี้จะหลีกเลี่ยงลูปสุดท้ายที่ไม่ได้ใช้ก่อนหน้านี้pattern += patternโดยไม่ต้องใช้การตรวจสอบราคาแพง ผลลัพธ์สุดท้ายจะเป็นดังนี้:

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;
};

และซอของซอร์ฟแวร์ต่อไปนี้เป็นเวอร์ชั่นที่ยังไม่ได้เผยแพร่: http://jsfiddle.net/wsdfg/


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

2
การต่อสตริงไม่แพงหรือ อย่างน้อยก็ในกรณีของ Java
Vijay Dev

ทำไมพวกเขาถึงเป็น อย่างไรก็ตามไม่สามารถปรับให้เหมาะสมได้ใน javarscript :(
McTrafik

สิ่งที่เกี่ยวกับการปรับปรุงประสิทธิภาพนี้: var r=s; for (var a=1;...:)))) อย่างไรก็ตามตามการทดสอบนี้ ( jsperf.com/string-repeat/2 ) ทำเรื่องง่าย ๆ สำหรับการวนลูปด้วยการต่อสตริงแบบเดียวกับสิ่งที่คุณแนะนำดูเหมือนว่าจะเร็วกว่า Chrome เมื่อเทียบกับการใช้ Array .join
Marco Demaio

@VijayDev - ไม่เป็นไปตามการทดสอบนี้: jsperf.com/ultimate-concat-vs-join
jbyrd

2

การทดสอบวิธีการต่าง ๆ :

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

นี่คือรุ่นที่ปลอดภัยของ JSLint

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

2

สำหรับเบราว์เซอร์ทั้งหมด

นี่เป็นเรื่องที่สั้นกระชับ:

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

หากคุณสนใจเรื่องประสิทธิภาพนี่เป็นวิธีที่ดีกว่ามาก:

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

หากคุณต้องการเปรียบเทียบประสิทธิภาพของตัวเลือกทั้งสองให้ดูซอนี้และซอนี้สำหรับการทดสอบเกณฑ์มาตรฐาน ระหว่างการทดสอบของฉันตัวเลือกที่สองนั้นเร็วกว่า Firefox ประมาณ 2 เท่าและ Chrome ก็เร็วขึ้น 4 เท่า!

สำหรับเบราว์เซอร์ที่ทันสมัยเท่านั้น:

ในเบราว์เซอร์สมัยใหม่คุณสามารถทำได้ดังนี้:

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

ตัวเลือกนี้ไม่เพียงสั้นกว่าตัวเลือกอื่น ๆ เท่านั้น แต่ยังเร็วกว่ากว่าตัวเลือกที่สอง

น่าเสียดายที่มันไม่ทำงานใน Internet Explorer เวอร์ชันใด ๆ ตัวเลขในตารางระบุเวอร์ชันเบราว์เซอร์แรกที่รองรับวิธีการอย่างสมบูรณ์:

ป้อนคำอธิบายรูปภาพที่นี่


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

คุณสามารถทดสอบได้ที่JSFiddle เปรียบเทียบกับแฮ็คArray.joinและของฉันพูดคร่าวๆ10 (Chrome) ถึง 100 (Safari) ถึง 200 (Firefox) เร็วขึ้น (ขึ้นอยู่กับเบราว์เซอร์)


2

ฟังก์ชั่นซ้ำอีกครั้ง:

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

2

ES2015ได้รับการตระหนักถึงrepeat()วิธีการนี้!

http://www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.repeat
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ สตริง / ซ้ำ
http://www.w3schools.com/jsref/jsref_repeat.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

นี่อาจเป็น recursive ที่เล็กที่สุด: -

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


1

เรียงต่อกันแบบวนซ้ำง่าย ๆ

ฉันแค่อยากจะทุบตีมันและทำสิ่งนี้:

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

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

ฉันไม่สามารถบอกได้ว่าฉันให้ความคิดมากและอาจแสดง :-)

นี่เป็นเนื้อหาที่ดีกว่า

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

"foo".ditto( 128 );

และมันก็เป็นเหมือนคำตอบที่โพสต์แล้ว - ฉันรู้เรื่องนี้

แต่ทำไมต้องเกิดซ้ำเลย?

และพฤติกรรมเริ่มต้นเล็กน้อยเช่นกัน?

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

"foo".ditto();

เพราะแม้ว่าวิธีการแบบเรียกซ้ำจะจัดการซ้ำขนาดใหญ่โดยพลการโดยไม่กดปุ่ม จำกัด การเรียกสแต็ก แต่ก็ช้ากว่ามาก

ทำไมฉันถึงรบกวนการเพิ่มวิธีการที่ไม่ฉลาดกว่าครึ่งเท่าที่โพสต์ไปแล้ว

ส่วนหนึ่งเพื่อความสนุกของตัวเองและบางส่วนชี้ให้เห็นอย่างง่ายที่สุดฉันรู้ว่ามีหลายวิธีในการสกินแมวและขึ้นอยู่กับสถานการณ์เป็นไปได้ว่าค่อนข้างดีที่สุดวิธีการที่นั้นไม่เหมาะ

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

วิธีการบางอย่างอาจจะน้อยกว่าการหาประโยชน์และเป็นเช่นนี้มีแนวโน้มที่จะถูกคงออกจากการดำรงอยู่และวิธีการอื่น ๆ อาจทำงานได้อย่างสวยงามในทุกสภาวะ แต่ถูกสร้างขึ้นเพื่อให้คนหนึ่งก็มีความคิดวิธีการทำงานไม่มี

"แล้วถ้าฉันทำไม่ได้มันทำงานอย่างไร!"

อย่างจริงจัง?

JavaScript ทนทุกข์จากจุดแข็งที่ยิ่งใหญ่ที่สุดอย่างหนึ่ง มันมีพฤติกรรมที่ไม่ดีอย่างมากและมีความยืดหยุ่นดังนั้นมันจะโค้งงอไปด้านหลังเพื่อให้ได้ผลลัพธ์เมื่อมันอาจจะดีกว่าสำหรับทุกคนถ้ามันหักมุม!

"ด้วยพลังอันยิ่งใหญ่มาพร้อมความรับผิดชอบที่ยอดเยี่ยม" ;-)

แต่ที่สำคัญกว่านั้นคือถึงแม้ว่าคำถามทั่วไปเช่นนี้จะนำไปสู่ความสุดยอดในรูปแบบของคำตอบที่ฉลาดว่าหากไม่มีสิ่งใดเพิ่มความรู้และขอบเขตอันไกลโพ้นในท้ายที่สุดงานในมือ - สคริปต์ภาคปฏิบัติที่ใช้วิธีการที่เกิดขึ้น - อาจต้องใช้น้อยกว่าหรือฉลาดกว่าเล็กน้อยกว่าที่แนะนำเล็กน้อย

อัลกอริทึม"สมบูรณ์แบบ"เหล่านี้สนุกและทั้งหมด แต่"หนึ่งขนาดเหมาะกับทุกคน"จะไม่ค่อยเกิดขึ้นถ้าดีกว่าช่างตัดเสื้อ

คำเทศนานี้ถูกนำเสนอให้คุณโดยไม่ต้องนอนหลับและไม่สนใจ ออกไปและรหัส!


1

ประการแรกคำถามของ OP ดูเหมือนจะเกี่ยวกับความกระชับ - ซึ่งฉันเข้าใจว่าหมายถึง "เรียบง่ายและอ่านง่าย" ในขณะที่คำตอบส่วนใหญ่ดูเหมือนจะเกี่ยวกับประสิทธิภาพ - ซึ่งเห็นได้ชัดว่าไม่ใช่เรื่องเดียวกันและฉันคิดว่าถ้าคุณใช้งานมาก อัลกอริทึมการจัดการข้อมูลขนาดใหญ่โดยเฉพาะไม่ควรกังวลเมื่อคุณใช้ฟังก์ชั่นการจัดการข้อมูล Javascript แบบพื้นฐาน ความรัดกุมสำคัญกว่ามาก

ประการที่สองเป็นAndré Laszlo สังเกต String.repeat เป็นส่วนหนึ่งของ ECMAScript ที่ 6 และที่มีอยู่แล้วในการใช้งานที่นิยมหลาย - เพื่อให้การดำเนินงานที่รัดกุมมากที่สุดของString.repeatไม่ได้ที่จะใช้มัน ;-)

สุดท้ายถ้าคุณต้องการสนับสนุนโฮสต์ที่ไม่มีการใช้ ECMAScript 6 โพลีฟิลของ MDN ที่André Laszlo พูดถึงนั้นเป็นอะไรที่กระชับ

ดังนั้นโดยไม่ต้องกังวลใจเพิ่มเติม - นี่คือpolyfill กระชับของฉัน:

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

ใช่นี่คือการสอบถามซ้ำ ฉันชอบการเรียกซ้ำ - มันง่ายและถ้าทำถูกต้องเข้าใจได้ง่าย เกี่ยวกับประสิทธิภาพหากภาษารองรับภาษานั้นจะมีประสิทธิภาพมากหากเขียนอย่างถูกต้อง

จากการทดสอบของฉันวิธีนี้ ~ 60% เร็วกว่าArray.joinวิธีการ แม้ว่ามันจะเห็นได้ชัดว่ามาจากที่ไหนการดำเนินงานของ disfated อย่างใกล้ชิดมันง่ายกว่าทั้งสอง

การตั้งค่าการทดสอบของฉันคือโหนด v0.10 โดยใช้ "โหมดเข้มงวด" (ฉันคิดว่ามันเปิดใช้งานTCOบางประเภท) เรียกrepeat(1000)ใช้สตริงอักขระ 10 ตัวต่อล้านครั้ง


1

หากคุณคิดว่าคำจำกัดความต้นแบบการสร้างอาร์เรย์และการดำเนินการเข้าร่วมนั้นมากเกินไปเพียงแค่ใช้รหัสบรรทัดเดียวที่คุณต้องการ String S ซ้ำ N ครั้ง:

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

3
รหัสควรอ่านได้ หากคุณเป็นเพียงแค่ทุกครั้งที่จะใช้มันให้จัดรูปแบบให้ถูกต้อง (หรือใช้Array(N + 1).join(str)วิธีการถ้าไม่ใช่คอขวดของประสิทธิภาพ) หากมีโอกาสน้อยที่สุดที่คุณจะใช้สองครั้งให้ย้ายไปยังฟังก์ชันที่ตั้งชื่ออย่างเหมาะสม
cloudfeet

1

ใช้ฟังก์ชันการทำงานของยูทิลิตี้ Lodash สำหรับ Javascript เช่นการทำซ้ำสตริง

Lodash ให้ประสิทธิภาพที่ดีและความเข้ากันได้กับ ECMAScript

ฉันขอแนะนำสำหรับการพัฒนา UI และใช้งานได้ดีกับฝั่งเซิร์ฟเวอร์เช่นกัน

ต่อไปนี้เป็นวิธีการทำซ้ำสตริง "yo" 2 ครั้งโดยใช้ Lodash:

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

0

โซลูชันแบบเรียกซ้ำโดยใช้การหารและพิชิต:

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

ฉันมาที่นี่แบบสุ่มและไม่เคยมีเหตุผลที่จะทำซ้ำอักขระในจาวาสคริปต์ก่อน

ฉันประทับใจกับวิธีการทำและผลที่ได้ของศิลปิน ฉันสังเกตเห็นว่าการต่อสตริงสุดท้ายนั้นไม่จำเป็นเพราะเดนนิสก็ชี้ให้เห็น

ฉันสังเกตเห็นอีกสองสามอย่างเมื่อเล่นกับการสุ่มตัวอย่างแบบไม่ใส่ชุด

ผลลัพธ์ที่ได้มีความหลากหลายในระดับที่เหมาะสมซึ่งมักนิยมใช้ในขั้นตอนสุดท้ายและอัลกอริธึมที่คล้ายกันมักจะจัดตำแหน่ง หนึ่งในสิ่งที่ฉันเปลี่ยนคือแทนที่จะใช้ JSLitmus ที่สร้างขึ้นนับเป็นเมล็ดสำหรับการโทร; เมื่อนับถูกสร้างขึ้นแตกต่างกันสำหรับวิธีการต่างๆฉันใส่ในดัชนี สิ่งนี้ทำให้สิ่งที่เชื่อถือได้มากขึ้น จากนั้นฉันดูที่การทำให้มั่นใจว่าสตริงที่มีขนาดต่างกันถูกส่งผ่านไปยังฟังก์ชั่น สิ่งนี้ช่วยป้องกันความผันแปรบางอย่างที่ฉันเห็นซึ่งอัลกอริทึมบางตัวทำได้ดีกว่าในตัวอักษรเดี่ยวหรือสตริงขนาดเล็ก อย่างไรก็ตามวิธีการ 3 อันดับแรกทั้งหมดทำได้ดีโดยไม่คำนึงถึงขนาดสตริง

ชุดทดสอบง่าม

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();

จากนั้นฉันก็รวมการแก้ไขของเดนนิสและตัดสินใจที่จะดูว่าฉันสามารถหาวิธีที่จะออกไปอีกเล็กน้อย

เนื่องจาก javascript ไม่สามารถปรับสิ่งต่าง ๆ ได้จริงวิธีที่ดีที่สุดในการปรับปรุงประสิทธิภาพคือการหลีกเลี่ยงสิ่งต่าง ๆ ด้วยตนเอง ถ้าฉันนำผลลัพธ์เล็ก ๆ น้อย ๆ 4 รายการแรกออกจากลูปฉันสามารถหลีกเลี่ยงร้านค้าสตริงได้ 2-4 แห่งและเขียนร้านค้าสุดท้ายไปยังผลลัพธ์โดยตรง

// 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;
}

สิ่งนี้ส่งผลให้ค่าเฉลี่ยดีขึ้นกว่าการแก้ไขของเดนนิส 1-2% อย่างไรก็ตามการเรียกใช้ที่แตกต่างกันและเบราว์เซอร์ที่แตกต่างกันจะแสดงความแปรปรวนพอสมควรว่ารหัสพิเศษนี้อาจไม่คุ้มค่ากับความพยายามของอัลกอริทึมก่อนหน้า 2

แผนภูมิ

แก้ไข: ฉันทำสิ่งนี้เป็นส่วนใหญ่ภายใต้โครเมียม Firefox และ IE มักจะให้ความช่วยเหลือเดนนิสด้วยสอง%



0

ผู้คนให้ความสำคัญกับเรื่องนี้มากเกินไปในระดับที่น่าหัวเราะหรือไร้ประสิทธิภาพ อาร์เรย์? recursion? คุณต้องล้อเล่นฉัน

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

แก้ไข ฉันใช้การทดสอบง่ายๆเพื่อเปรียบเทียบกับเวอร์ชัน bitwise ที่โพสต์โดย artistoex / disfated และกลุ่มคนอื่น ๆ หลังนั้นเร็วกว่าเพียงเล็กน้อย แต่คำสั่งที่มีขนาดใหญ่ขึ้นมีประสิทธิภาพของหน่วยความจำมากขึ้น สำหรับ 1000000 คำซ้ำ 'blah' กระบวนการโหนดเพิ่มสูงถึง 46 เมกะไบต์ด้วยอัลกอริทึมการต่อข้อมูลอย่างง่าย (ด้านบน) แต่เพียง 5.5 เมกะไบต์ด้วยอัลกอริทึมลอการิทึม หลังเป็นวิธีที่จะไปแน่นอน โพสต์ใหม่เพื่อความชัดเจน:

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

คุณมีเวลาเหลือเฟือstring += stringครึ่งหนึ่ง
nikolay

0

การต่อสตริงเข้าด้วยกันตามจำนวน

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

หวังว่าจะช่วย!


0

ด้วย ES8 คุณสามารถใช้padStartหรือpadEndสิ่งนี้ เช่น.

var str = 'cat';
var num = 23;
var size = str.length * num;
"".padStart(size, str) // outputs: 'catcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcat'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.