อะไรคือวิธีที่ดีที่สุดหรือรัดกุมที่สุดในการคืนค่าสตริงซ้ำจำนวนเท่าใดก็ได้?
ต่อไปนี้เป็นภาพที่ดีที่สุดของฉัน:
function repeat(s, n){
var a = [];
while(a.length < n){
a.push(s);
}
return a.join('');
}
อะไรคือวิธีที่ดีที่สุดหรือรัดกุมที่สุดในการคืนค่าสตริงซ้ำจำนวนเท่าใดก็ได้?
ต่อไปนี้เป็นภาพที่ดีที่สุดของฉัน:
function repeat(s, n){
var a = [];
while(a.length < n){
a.push(s);
}
return a.join('');
}
คำตอบ:
หมายเหตุถึงผู้อ่านใหม่:คำตอบนี้เก่าและไม่สามารถนำไปใช้ได้จริง - เป็นเพียง "ฉลาด" เพราะใช้ Array stuff เพื่อทำสิ่งต่างๆให้สำเร็จ เมื่อฉันเขียนว่า "กระบวนการที่น้อยลง" ฉันหมายถึง "รหัสน้อยลง" เพราะอย่างที่คนอื่น ๆ ได้กล่าวไว้ในคำตอบที่ตามมามันจะทำงานเหมือนหมู ดังนั้นอย่าใช้มันหากความเร็วสำคัญกับคุณ
ฉันจะวางฟังก์ชั่นนี้ลงในวัตถุ String โดยตรง แทนที่จะสร้างอาร์เรย์เติมและเข้าร่วมกับถ่านเปล่าเพียงแค่สร้างอาร์เรย์ที่มีความยาวที่เหมาะสมและเข้าร่วมกับสตริงที่คุณต้องการ ผลลัพธ์เดียวกันกระบวนการน้อยลง!
String.prototype.repeat = function( num )
{
return new Array( num + 1 ).join( this );
}
alert( "string to repeat\n".repeat( 4 ) );
String.repeat = function(string, num){ return new Array(parseInt(num) + 1).join(string); };
แทนเช่นนี้ เรียกว่าเป็นอย่างนี้:String.repeat('/\', 20)
ฉันได้ทดสอบประสิทธิภาพของวิธีการที่เสนอทั้งหมดแล้ว
นี่คือตัวแปรที่เร็วที่สุดที่ฉันมี
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 สิ่ง:
pattern = this
ด้วยpattern = this.valueOf()
(ล้างการแปลงประเภทที่ชัดเจนหนึ่ง);if (count < 1)
ตรวจสอบจากprototypejsไปยังด้านบนของฟังก์ชันเพื่อแยกการกระทำที่ไม่จำเป็นในกรณีนั้นUPD
สร้างสนามทดสอบประสิทธิภาพเล็กน้อยที่นี่สำหรับผู้ที่สนใจ
ตัวแปรcount
~ 0 .. 100:
ค่าคงที่count
= 1024:
ใช้มันและทำให้เร็วขึ้นถ้าคุณทำได้ :)
count < 1
กรณีนี้คือการเพิ่มประสิทธิภาพที่ไม่จำเป็นจริงๆ
ปัญหานี้เป็นปัญหาการปรับให้เหมาะสมที่รู้จักกันดี / "คลาสสิก" สำหรับ JavaScript ซึ่งเกิดจากข้อเท็จจริงที่ว่าสตริง JavaScript เป็น "ไม่เปลี่ยนรูป" และนอกจากนี้โดยการเชื่อมต่อของอักขระแม้แต่ตัวเดียวกับสตริงที่ต้องการสร้างรวมถึงการจัดสรรหน่วยความจำ สตริงใหม่ทั้งหมด
น่าเสียดายที่คำตอบที่ยอมรับในหน้านี้ผิดที่ "ผิด" หมายถึงปัจจัยประสิทธิภาพ 3x สำหรับสายอักขระตัวเดียวอย่างง่ายและ 8x-97x สำหรับสตริงสั้น ๆ ซ้ำหลายครั้งเป็น 300x สำหรับการทำซ้ำประโยคและผิดอย่างไม่สิ้นสุดเมื่อ การ จำกัด อัตราส่วนของความซับซ้อนของอัลกอริธึมที่n
จะไม่มีที่สิ้นสุด นอกจากนี้ยังมีคำตอบในหน้านี้อีกซึ่งเกือบจะถูก (ขึ้นอยู่กับหนึ่งในหลาย ๆ รุ่นและรูปแบบของการแก้ปัญหาที่ถูกต้องซึ่งแพร่กระจายไปทั่วอินเทอร์เน็ตใน 13 ปีที่ผ่านมา) อย่างไรก็ตามโซลูชัน "เกือบถูกต้อง" นี้พลาดจุดสำคัญของอัลกอริธึมที่ถูกต้องทำให้ประสิทธิภาพลดลง 50%
~ ตุลาคม 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()
ฟังก์ชั่นเป็นอะไรก็ได้ยกเว้นการทำซ้ำอักขระเดี่ยว
แม้แต่ตัวอักษรเดียวไม่สามารถใช้ในกรณีที่พวกเขามีไบต์หลายเช่นนิติบุคคลของ 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 >>= 1
n = 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 แบ่งตำแหน่งอย่างระมัดระวังสำหรับการควบคุมลูป หลีกเลี่ยงการทำซ้ำสตริงอย่างไม่จำเป็นชี้แจงหนึ่งครั้งโดยไม่จำเป็น
อันนี้ค่อนข้างมีประสิทธิภาพ
String.prototype.repeat = function(times){
var result="";
var pattern=this;
while (times > 0) {
if (times&1)
result+=pattern;
times>>=1;
pattern+=pattern;
}
return result;
};
ข่าวดี! String.prototype.repeat
เป็นส่วนหนึ่งของจาวาสคริปต์
"yo".repeat(2);
// returns: "yoyo"
เบราว์เซอร์หลักทั้งหมดรองรับวิธีนี้ยกเว้น Internet Explorer และ Android Webview ได้ถึงวันที่รายการให้ดูMDN: String.prototype.repeat> เข้ากันได้ของเบราว์เซอร์
MDN มีpolyfillสำหรับเบราว์เซอร์ที่ไม่มีการสนับสนุน
String.prototype.repeatตอนนี้เป็นมาตรฐาน ES6
'abc'.repeat(3); //abcabcabc
การขยายโซลูชันของ 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
ความคิดที่หรูหราของเขา!
String.prototype.repeat = function(n){return new Array(isNaN(n) ? 1 : ++n).join(this);}
ใช้ Array(N+1).join("string_to_repeat")
/**
@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
นี่คือการปรับปรุง 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/
function repeat(s, n) { var r=""; for (var a=0;a<n;a++) r+=s; return r;}
var r=s; for (var a=1;...
:)))) อย่างไรก็ตามตามการทดสอบนี้ ( jsperf.com/string-repeat/2 ) ทำเรื่องง่าย ๆ สำหรับการวนลูปด้วยการต่อสตริงแบบเดียวกับสิ่งที่คุณแนะนำดูเหมือนว่าจะเร็วกว่า Chrome เมื่อเทียบกับการใช้ Array .join
การทดสอบวิธีการต่าง ๆ :
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));
}
}
นี่คือรุ่นที่ปลอดภัยของ JSLint
String.prototype.repeat = function (num) {
var a = [];
a.length = num << 0 + 1;
return a.join(this);
};
นี่เป็นเรื่องที่สั้นกระชับ:
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 เวอร์ชันใด ๆ ตัวเลขในตารางระบุเวอร์ชันเบราว์เซอร์แรกที่รองรับวิธีการอย่างสมบูรณ์:
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) เร็วขึ้น (ขึ้นอยู่กับเบราว์เซอร์)
ฟังก์ชั่นซ้ำอีกครั้ง:
function repeat(s, n) {
var str = '';
for (var i = 0; i < n; i++) {
str += s;
}
return str;
}
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
นี่อาจเป็น recursive ที่เล็กที่สุด: -
String.prototype.repeat = function(n,s) {
s = s || ""
if(n>0) {
s += this
s = this.repeat(--n,s)
}
return s}
ซอ: http://jsfiddle.net/3Y9v2/
function repeat(s, n){
return ((new Array(n+1)).join(s));
}
alert(repeat('R', 10));
ฉันแค่อยากจะทุบตีมันและทำสิ่งนี้:
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 ทนทุกข์จากจุดแข็งที่ยิ่งใหญ่ที่สุดอย่างหนึ่ง มันมีพฤติกรรมที่ไม่ดีอย่างมากและมีความยืดหยุ่นดังนั้นมันจะโค้งงอไปด้านหลังเพื่อให้ได้ผลลัพธ์เมื่อมันอาจจะดีกว่าสำหรับทุกคนถ้ามันหักมุม!
"ด้วยพลังอันยิ่งใหญ่มาพร้อมความรับผิดชอบที่ยอดเยี่ยม" ;-)
แต่ที่สำคัญกว่านั้นคือถึงแม้ว่าคำถามทั่วไปเช่นนี้จะนำไปสู่ความสุดยอดในรูปแบบของคำตอบที่ฉลาดว่าหากไม่มีสิ่งใดเพิ่มความรู้และขอบเขตอันไกลโพ้นในท้ายที่สุดงานในมือ - สคริปต์ภาคปฏิบัติที่ใช้วิธีการที่เกิดขึ้น - อาจต้องใช้น้อยกว่าหรือฉลาดกว่าเล็กน้อยกว่าที่แนะนำเล็กน้อย
อัลกอริทึม"สมบูรณ์แบบ"เหล่านี้สนุกและทั้งหมด แต่"หนึ่งขนาดเหมาะกับทุกคน"จะไม่ค่อยเกิดขึ้นถ้าดีกว่าช่างตัดเสื้อ
คำเทศนานี้ถูกนำเสนอให้คุณโดยไม่ต้องนอนหลับและไม่สนใจ ออกไปและรหัส!
ประการแรกคำถามของ 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 ตัวต่อล้านครั้ง
หากคุณคิดว่าคำจำกัดความต้นแบบการสร้างอาร์เรย์และการดำเนินการเข้าร่วมนั้นมากเกินไปเพียงแค่ใช้รหัสบรรทัดเดียวที่คุณต้องการ String S ซ้ำ N ครั้ง:
for (var i = 0, result = ''; i < N; i++) result += S;
Array(N + 1).join(str)
วิธีการถ้าไม่ใช่คอขวดของประสิทธิภาพ) หากมีโอกาสน้อยที่สุดที่คุณจะใช้สองครั้งให้ย้ายไปยังฟังก์ชันที่ตั้งชื่ออย่างเหมาะสม
ใช้ฟังก์ชันการทำงานของยูทิลิตี้ Lodash สำหรับ Javascript เช่นการทำซ้ำสตริง
Lodash ให้ประสิทธิภาพที่ดีและความเข้ากันได้กับ ECMAScript
ฉันขอแนะนำสำหรับการพัฒนา UI และใช้งานได้ดีกับฝั่งเซิร์ฟเวอร์เช่นกัน
ต่อไปนี้เป็นวิธีการทำซ้ำสตริง "yo" 2 ครั้งโดยใช้ Lodash:
> _.repeat('yo', 2)
"yoyo"
โซลูชันแบบเรียกซ้ำโดยใช้การหารและพิชิต:
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); }
}
ฉันมาที่นี่แบบสุ่มและไม่เคยมีเหตุผลที่จะทำซ้ำอักขระในจาวาสคริปต์ก่อน
ฉันประทับใจกับวิธีการทำและผลที่ได้ของศิลปิน ฉันสังเกตเห็นว่าการต่อสตริงสุดท้ายนั้นไม่จำเป็นเพราะเดนนิสก็ชี้ให้เห็น
ฉันสังเกตเห็นอีกสองสามอย่างเมื่อเล่นกับการสุ่มตัวอย่างแบบไม่ใส่ชุด
ผลลัพธ์ที่ได้มีความหลากหลายในระดับที่เหมาะสมซึ่งมักนิยมใช้ในขั้นตอนสุดท้ายและอัลกอริธึมที่คล้ายกันมักจะจัดตำแหน่ง หนึ่งในสิ่งที่ฉันเปลี่ยนคือแทนที่จะใช้ 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 มักจะให้ความช่วยเหลือเดนนิสด้วยสอง%
วิธีง่าย ๆ :
String.prototype.repeat = function(num) {
num = parseInt(num);
if (num < 0) return '';
return new Array(num + 1).join(this);
}
ผู้คนให้ความสำคัญกับเรื่องนี้มากเกินไปในระดับที่น่าหัวเราะหรือไร้ประสิทธิภาพ อาร์เรย์? 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
ครึ่งหนึ่ง
การต่อสตริงเข้าด้วยกันตามจำนวน
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));
หวังว่าจะช่วย!
ด้วย ES8 คุณสามารถใช้padStart
หรือpadEnd
สิ่งนี้ เช่น.
var str = 'cat';
var num = 23;
var size = str.length * num;
"".padStart(size, str) // outputs: 'catcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcat'