Keyfi bir kez tekrarlanan bir dize döndürmek için en iyi veya en özlü yöntem nedir?
Şimdiye kadarki en iyi çekimim:
function repeat(s, n){
var a = [];
while(a.length < n){
a.push(s);
}
return a.join('');
}
Keyfi bir kez tekrarlanan bir dize döndürmek için en iyi veya en özlü yöntem nedir?
Şimdiye kadarki en iyi çekimim:
function repeat(s, n){
var a = [];
while(a.length < n){
a.push(s);
}
return a.join('');
}
Yanıtlar:
Yeni okuyucular için not: Bu cevap eski ve çok pratik değil - bu sadece "zekice" çünkü String şeyler yapmak için Array şeyler kullanır. "Daha az işlem" yazdığımda, kesinlikle "daha az kod" demek istedim, çünkü sonraki yanıtlarda belirttiği gibi, bir domuz gibi çalışır. Bu yüzden hız sizin için önemliyse kullanmayın.
Bu işlevi doğrudan String nesnesine koyardım. Bir dizi oluşturmak, doldurmak ve boş bir karakterle birleştirmek yerine, uygun uzunlukta bir dizi oluşturmak ve istediğiniz dizeyle birleştirmek yeterlidir. Aynı sonuç, daha az işlem!
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)
Önerilen tüm yaklaşımların performansını test ettim.
İşte sahip olduğum en hızlı varyant .
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;
};
Veya tek başına işlev olarak:
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 algoritmasına dayanır . Gerçekten hızlı. Ve ne kadar büyük olursa count
, geleneksel new Array(count + 1).join(string)
yaklaşımla karşılaştırıldığında o kadar hızlı gider .
Sadece 2 şeyi değiştirdim:
pattern = this
ile pattern = this.valueOf()
(temizler bir belirgin tipi dönüşümü);if (count < 1)
kontrol ekledi .UPD
İlgilenenler için burada küçük bir performans testi oyun alanı oluşturduk.
değişken count
~ 0 .. 100:
sabit count
= 1024:
Kullanın ve mümkünse daha hızlı yapın :)
count < 1
Durumun gerçekten gereksiz bir optimizasyon olduğunu düşünüyorum .
Bu sorun, JavaScript dizelerinin "değiştirilemez" olmasından ve JavaScript için tek bir karakterin bile birleştirilmesiyle eklenmesinin bilinen / "klasik" bir optimizasyon sorunudur. , tamamen yeni bir dize.
Ne yazık ki, bu sayfadaki kabul edilen cevap yanlıştır, burada "yanlış", basit tek karakterli dizeler için 3x ve daha fazla tekrarlanan kısa dizeler için 8x-97x, tekrarlanan cümleler için 300x'e ve tekrarlandığında sonsuz yanlış anlamına gelir. n
sonsuzluğa giderken algoritmaların karmaşıklık oranlarının sınırını almak . Ayrıca, bu sayfada neredeyse doğru olan başka bir cevap daha var (son 13 yıl içinde internette dolaşan birçok neslin ve doğru çözümün varyasyonlarından birine dayanarak). Ancak bu "neredeyse doğru" çözüm, doğru algoritmanın% 50 performans düşüşüne neden olan kilit noktasını kaçırmaktadır.
~ Ekim 2000 Bu sorun için geniş çapta uyarlanmış, değiştirilmiş, daha sonra iyi anlaşılmamış ve unutulmuş bir algoritma yayınladım. Bu sorunu çözmek için, 2008 yılının Ağustos ayında algoritmayı açıklayan ve genel amaçlı JavaScript optimizasyonlarının basit bir örneği olarak kullanan bir http://www.webreference.com/programming/javascript/jkm3/3.html bir makale yayınladım . Şimdiye kadar, Web Başvurusu iletişim bilgilerimi ve hatta bu makaledeki adımı temizledi. Ve bir kez daha, algoritma geniş çapta uyarlandı, değiştirildi, daha sonra iyi anlaşılmadı ve büyük ölçüde unutuldu.
Orijinal dize tekrarlama / çarpma Joseph Myers tarafından JavaScript algoritması. Ağustos 2008'de bu başvuru ile Web Reference tarafından yayımlanmıştır: http://www.webreference.com/programming/javascript/jkm3/3.html (Makale işlevi, yalnızca garip olanlar için JavaScript optimizasyonlarına örnek olarak kullanılmıştır. adı "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;
}
Bu makalenin yayınlanmasından sonraki iki ay içinde, aynı soru Stack Overflow'a gönderildi ve görünüşe göre bu problemin orijinal algoritması bir kez daha unutulduğunda radarımın altında uçtu. Bu Yığın Taşması sayfasında bulunan en iyi çözüm, çözümümün muhtemelen birkaç nesille ayrılmış değiştirilmiş bir sürümüdür. Ne yazık ki değişiklikler modifikasyonun bozulmasına neden oldu. Aslında, halkanın yapısını orijinalimden değiştirerek, modifiye edilmiş çözüm, üstel çoğaltma işleminin tamamen gereksiz bir ekstra adımını gerçekleştirir (böylece uygun cevapta kullanılan en büyük dizgiyi kendisiyle bir ekstra zaman birleştirir ve sonra atar).
Aşağıda, bu sorunun tüm yanıtları ve herkesin yararına olan bazı JavaScript optimizasyonları hakkında bir tartışma sunulmaktadır.
Bu tekniğin nasıl çalıştığını göstermek için, gereken uzunlukta dizeler oluşturan gerçek hayattaki bir JavaScript işlevi kullanıyoruz. Göreceğimiz gibi, daha fazla optimizasyon eklenebilir!
Burada kullanılan gibi bir işlev, metin sütunlarını hizalamak, para biçimlendirmek veya blok verilerini sınıra kadar doldurmak için dolgu oluşturmaktır. Metin oluşturma işlevi, metin üzerinde çalışan diğer işlevleri test etmek için değişken uzunluklu girdilere de izin verir. Bu işlev, JavaScript metin işleme modülünün önemli bileşenlerinden biridir.
Devam ederken, orijinal kodu dizeler oluşturmak için optimize edilmiş bir algoritmaya dönüştürürken en önemli optimizasyon tekniklerinden iki tanesini daha ele alacağız. Nihai sonuç, her yerde kullandığım endüstriyel güçte, yüksek performanslı bir işlevdir - JavaScript sipariş formlarında, veri biçimlendirmesinde ve e-posta / metin mesajı biçimlendirmesinde ve diğer birçok kullanımda öğe fiyatlarını ve toplamları hizalar.
Dizeler oluşturmak için orijinal kod stringFill1()
function stringFill1(x, n) {
var s = '';
while (s.length < n) s += x;
return s;
}
/* Example of output: stringFill1('x', 3) == 'xxx' */
Sözdizimi açıktır. Gördüğünüz gibi, daha fazla optimizasyona geçmeden önce zaten yerel işlev değişkenlerini kullandık.
Koddaki bir nesne özelliğine s.length
, performansına zarar veren masum bir başvuru olduğunu unutmayın. Daha da kötüsü, bu nesne özelliğinin kullanımı, okuyucunun JavaScript dize nesnelerinin özellikleri hakkında bildiklerini varsayarak programın basitliğini azaltır.
Bu object özelliğinin kullanılması, bilgisayar programının genelliğini yok eder. Program x
bir uzunluk dizesi olması gerektiğini varsayar . Bu, stringFill1()
işlevin uygulanmasını tekli karakterlerin tekrarı dışında herhangi bir şeye sınırlar . HTML varlığı gibi birden çok bayt içeriyorsa, tek karakterler bile kullanılamaz
.
Bir nesne özelliğinin bu gereksiz kullanımından kaynaklanan en kötü sorun, işlevin boş bir giriş dizesinde test edilmesi durumunda sonsuz bir döngü oluşturmasıdır x
. Genelliği kontrol etmek için, mümkün olan en küçük girdi miktarına bir program uygulayın. Kullanılabilir bellek miktarını aşması istendiğinde çöken bir programın mazereti vardır. Hiçbir şey üretmemesi istendiğinde bunun gibi bir program kabul edilemez. Bazen güzel kod zehirli koddur.
Basitlik, bilgisayar programlamanın belirsiz bir hedefi olabilir, ancak genellikle değildir. Bir program makul düzeyde genelliğe sahip olmadığında, "Program gittiği kadar iyidir." Demek geçerli değildir. Gördüğünüz gibi, string.length
özelliği kullanmak bu programın genel bir ortamda çalışmasını engeller ve aslında yanlış program bir tarayıcıya veya sistem çökmesine neden olmaya hazırdır.
Bu JavaScript'in performansını artırmanın ve bu iki ciddi soruna dikkat etmenin bir yolu var mı?
Elbette. Sadece tamsayıları kullanın.
Dizeler oluşturmak için optimize edilmiş kod stringFill2()
function stringFill2(x, n) {
var s = '';
while (n-- > 0) s += x;
return s;
}
Karşılaştırılacak zamanlama kodu stringFill1()
vestringFill2()
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);
Şimdiye kadarki başarı stringFill2()
stringFill1()
100 baytlık bir dizeyi doldurmak için 47.297 mikrosaniye (saniyenin milyonda biri) stringFill2()
alır ve aynı şeyi yapmak için 27,68 mikrosaniye alır. Bu, bir nesne özelliğine başvurudan kaçınarak performansta neredeyse iki katına çıkar.
Önceki sonucumuz iyi görünüyordu - aslında çok iyi. Geliştirilmiş fonksiyon stringFill2()
, ilk iki optimizasyonumuzun kullanılması nedeniyle çok daha hızlıdır. Size şimdi olduğundan çok daha hızlı olmanın iyileştirilebileceğini söylersem buna inanır mıydınız?
Evet, bu hedefe ulaşabiliriz. Şu anda uzun dizelere kısa dizeler eklemekten nasıl kaçındığımızı açıklamamız gerekiyor.
Kısa vadeli davranış orijinal fonksiyonumuzla karşılaştırıldığında oldukça iyi görünmektedir. Bilgisayar bilimcileri, bir fonksiyonun veya bilgisayar programı algoritmasının "asimptotik davranışını" analiz etmeyi sever, bu da uzun vadeli davranışını daha büyük girdilerle test ederek incelemek anlamına gelir. Bazen başka testler yapılmadan, bir bilgisayar programının geliştirilebileceğinin hiçbir zaman farkında olmaz. Ne olacağını görmek için 200 baytlık bir dize oluşturacağız.
İle ortaya çıkan sorun stringFill2()
Zamanlama fonksiyonumuzu kullanarak, 200 baytlık bir dize için sürenin 62.54 mikrosaniyeye, 100 baytlık bir dize için 27.68'e yükseldiğini görüyoruz. Öyle görünüyor ki, iki kat daha fazla iş yapmak için zaman iki katına çıkmalı, ancak bunun yerine üç kat veya dört katına çıktı. Programlama deneyiminden, bu sonuç garip görünüyor, çünkü herhangi bir şey varsa, iş daha verimli bir şekilde yapıldığından işlev biraz daha hızlı olmalıdır (işlev çağrısı başına 100 bayt yerine işlev çağrısı başına 200 bayt). Bu sorun, JavaScript dizelerinin sinsi bir özelliği ile ilgilidir: JavaScript dizeleri "değişmez" dir.
Değişmez, bir dizeyi oluşturulduktan sonra değiştiremeyeceğiniz anlamına gelir. Her seferinde bir bayt ekleyerek, bir bayt daha fazla çaba harcamayız. Aslında tüm dizeyi artı bir bayt daha oluşturuyoruz.
Aslında, 100 baytlık bir dizeye bir bayt daha eklemek için 101 bayt değerinde iş gerekir. Bir N
bayt dizisi oluşturmak için hesaplama maliyetini kısaca analiz edelim . İlk baytı eklemenin maliyeti 1 birim hesaplama çabasıdır. İkinci bayt eklemenin maliyeti bir birim değil 2 birimdir (ilk baytı yeni bir dize nesnesine kopyalamak ve ikinci baytı eklemek). Üçüncü bayt için 3 birim vb.
C(N) = 1 + 2 + 3 + ... + N = N(N+1)/2 = O(N^2)
. Sembol O(N^2)
, N kare büyük O olarak telaffuz edilir ve uzun vadede hesaplama maliyetinin dize uzunluğunun karesiyle orantılı olduğu anlamına gelir. 100 karakter oluşturmak için 10.000 adet iş gerekir ve 200 karakter oluşturmak için 40.000 adet iş gerekir.
Bu yüzden 100 karakterden 200 karakter oluşturmak iki kattan daha uzun sürdü. Aslında, dört kat daha uzun sürmüş olmalıydı. Programlama deneyimimiz, işin daha uzun dizeler için biraz daha verimli bir şekilde yapılması ve dolayısıyla sadece üç kat daha uzun sürmesi açısından doğruydu. İşlev çağrısının ek yükü, oluşturduğumuz bir dizenin uzunluğu konusunda önemsiz hale geldiğinde, aslında bir dizenin iki katı uzunluğunda bir dizenin oluşturulması dört kat daha fazla zaman alır.
(Tarihsel not: Bu analiz, kaynak koddaki dizeler için geçerli değildir html = 'abcd\n' + 'efgh\n' + ... + 'xyz.\n'
, çünkü JavaScript kaynak kodu derleyicisi bir JavaScript dizesi nesnesine dönüştürülmeden önce dizeleri birleştirebilir. Sadece birkaç yıl önce KJS uygulaması Artı işaretleriyle birleştirilen uzun kaynak kodu dizeleri yüklenirken JavaScript donuyor veya çöküyor Hesaplama süresinden bu yana O(N^2)
, KJS JavaScript motor çekirdeğini kullanan Konqueror Web tarayıcısını veya Safari'yi aşırı yükleyen Web sayfaları yapmak zor değildi. bir biçimlendirme dili ve JavaScript biçimlendirme dili ayrıştırıcısı geliştirirken bu sorunla karşılaştım ve ardından JavaScript İçerdiği komut dosyasını yazdığımda soruna neyin neden olduğunu keşfettim.)
Açıkçası, bu hızlı performans bozulması büyük bir sorundur. JavaScript'in dizeleri değiştirilemez nesneler olarak işleme biçimini değiştiremediğimiz için bununla nasıl başa çıkabiliriz? Çözüm, dizeyi mümkün olduğunca az sayıda yeniden oluşturan bir algoritma kullanmaktır.
Açıklığa kavuşturmak için hedefimiz, uzun dizelere kısa dizeler eklemekten kaçınmaktır, çünkü kısa dizeyi eklemek için tüm uzun dizenin de çoğaltılması gerekir.
Uzun dizelere kısa dizeler eklemekten kaçınmak için algoritma nasıl çalışır?
Yeni dize nesnelerinin oluşturulma sayısını azaltmanın iyi bir yolu. Çıktıya bir seferde birden fazla bayt eklenecek şekilde daha uzun dize uzunluklarını birleştirin.
Örneğin, bir uzunluk dizesi yapmak için 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 */
Bunu yapmak için 1 uzunluklu bir dize oluşturmak, 2 uzunluklu bir dize oluşturmak, 4 uzunluklu bir dize oluşturmak, 8 uzunluklu bir dize oluşturmak ve son olarak 9 uzunluklu bir dize oluşturmak gerekir. Ne kadar maliyet tasarrufu sağladık?
Eski maliyet C(9) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 9 = 45
.
Yeni maliyet C(9) = 1 + 2 + 4 + 8 + 9 = 24
.
0 uzunluğunda bir dizeye 1 uzunluğunda bir dize, 1 uzunluğunda bir dize 1 uzunluğunda bir dize, 2 uzunluğunda bir dizeye 2 uzunluğunda bir dize, ardından 4 uzunluğunda bir dize eklememiz gerektiğine dikkat edin. bir uzunluk dizesi 4'e, daha sonra bir uzunluk dizesi elde etmek için 8 uzunluk dizesine 1 uzunluk dizesine 9, yaptığımız şey uzun dizelere kısa dizeler eklemekten kaçınmak veya eşit, neredeyse eşit uzunlukta dizeleri birleştirmeye çalışıyor.
Eski hesaplama maliyeti için bir formül bulduk N(N+1)/2
. Yeni maliyet için bir formül var mı? Evet, ama karmaşık. Önemli olan şey, bu O(N)
nedenle dize uzunluğunu iki katına çıkarmak, dört katına çıkarmak yerine iş miktarını yaklaşık iki katına çıkaracaktır.
Bu yeni fikri uygulayan kod, hesaplama maliyetinin formülü kadar karmaşıktır. Okuduğunuzda, bunun >>= 1
1 bayt sağa kaydırılması anlamına geldiğini unutmayın . Yani n = 10011
bir ikili sayı n >>= 1
ise, değerle sonuçlanır n = 1001
.
Kodun tanıyamayacağınız diğer kısmı, bitsel ve operatördür, yazılmıştır &
. İfadesinin n & 1
son ikili basamağı n
1 ise true, son ikili basamağı n
0 ise false olarak değerlendirilir .
Yeni yüksek verimli stringFill3()
işlev
function stringFill3(x, n) {
var s = '';
for (;;) {
if (n & 1) s += x;
n >>= 1;
if (n) x += x;
else break;
}
return s;
}
Eğitimsiz göze çirkin görünüyor, ancak performansı hoş olmaktan daha az bir şey değil.
Bu işlevin ne kadar iyi performans gösterdiğini görelim. Sonuçları gördükten sonra, bir O(N^2)
algoritma ile bir O(N)
algoritma arasındaki farkı asla unutmayacaksınız .
stringFill1()
200 baytlık bir dize oluşturmak için 88.7 mikrosaniye (saniyenin milyonda biri) stringFill2()
alır, 62.54 stringFill3()
alır ve yalnızca 4.608 alır. Bu algoritmayı daha iyi yapan nedir? Tüm işlevler yerel işlev değişkenlerini kullanmaktan faydalandı, ancak ikinci ve üçüncü optimizasyon tekniklerinden yararlanmak performansına yirmi kat iyileşme sağladı stringFill3()
.
Daha derin analiz
Bu özel işlevi rekabeti suyun dışına iten şey nedir?
Daha önce de bahsettiğim gibi, bu işlevlerin her ikisi nedeni, stringFill1()
ve stringFill2()
bu yüzden yavaş çalışmasına JavaScript dizeleri değişmez olmasıdır. JavaScript tarafından saklanan dize verilerine bir seferde bir bayt daha eklenmesine izin vermek için bellek yeniden tahsis edilemez. Dizenin sonuna bir bayt daha eklendiğinde, dizenin tamamı baştan sona yeniden oluşturulur.
Bu nedenle, komut dosyasının performansını iyileştirmek için, önceden iki dizeyi birleştirerek ve sonra istenen dize uzunluğunu yinelemeli olarak oluşturarak daha uzun uzunluklu dizeleri önceden hesaplamak gerekir.
Örneğin, 16 harfli bir bayt dizesi oluşturmak için, önce iki baytlık bir dize önceden hesaplanır. Daha sonra dört baytlık bir dizeyi önceden hesaplamak için iki baytlık dize yeniden kullanılır. Ardından, sekiz baytlık bir dizeyi önceden hesaplamak için dört baytlık dize yeniden kullanılır. Son olarak, sekiz baytlık iki dizgi, 16 baytlık istenen yeni dizeyi oluşturmak için yeniden kullanılabilir. Toplamda 2 yeni, biri 4 uzunluğunda, biri 8 uzunluğunda ve 16 uzunluğunda olmak üzere dört yeni dizenin oluşturulması gerekiyordu. Toplam maliyet 2 + 4 + 8 + 16 = 30'dur.
Uzun vadede bu verimlilik, ters sırayla eklenerek ve ilk terim a1 = N ile başlayan ve ortak r = 1/2 oranına sahip geometrik bir seri kullanılarak hesaplanabilir. Geometrik bir serinin toplamı tarafından verilir a_1 / (1-r) = 2N
.
Bu, 16'ya kadar yeni bir uzunluk 2 dizesi oluşturmak, 3, 4, 5 vb. Uzunluğunda yeni bir dize oluşturmak için bir karakter eklemekten daha etkilidir. Önceki algoritma, bir seferde tek bir bayt ekleme işlemini kullandı. ve toplam maliyeti olurdu n (n + 1) / 2 = 16 (17) / 2 = 8 (17) = 136
.
Açıkçası, 136 30'dan çok daha büyük bir sayıdır ve bu nedenle önceki algoritma bir dize oluşturmak için çok, çok daha fazla zaman alır.
İki yöntemi karşılaştırmak için, özyinelemeli algoritmanın ("böl ve fethet" de denir) 123.457 uzunluğunda bir dizede ne kadar hızlı olduğunu görebilirsiniz. FreeBSD bilgisayarımda stringFill3()
işlevde uygulanan bu algoritma dizeyi 0.001058 saniyede stringFill1()
oluştururken, orijinal işlev dizeyi 0.0808 saniyede oluşturur. Yeni işlev 76 kat daha hızlı.
Dizginin uzunluğu büyüdükçe performans farkı artar. Daha büyük ve daha büyük dizeler oluşturuldukça sınırda, orijinal işlev kabaca C1
(sabit) süreler N^2
gibi davranır ve yeni işlev C2
(sabit) süreler gibi davranır N
.
Deneyimizden, C1
olmanın C1 = 0.0808 / (123457)2 = .00000000000530126997
değerini ve C2
olmanın değerini belirleyebiliriz C2 = 0.001058 / 123457 = .00000000856978543136
. 10 saniye içinde, yeni işlev 1.166.890.359 karakter içeren bir dize oluşturabilir. Aynı dizeyi oluşturmak için eski işlevin 7.218.384 saniyeye ihtiyacı vardır.
Bu on saniyeye kıyasla neredeyse üç ay!
Sadece (birkaç yıl geç) cevap veriyorum çünkü bu soruna orijinal çözümüm 10 yıldan fazla bir süredir internette yüzüyor ve görünüşe göre hala hatırlayan az sayıda kişi tarafından anlaşılamıyor. Bu konuda bir makale yazarak yardımcı olacağımı düşündüm:
Yüksek Hızlı JavaScript için Performans Optimizasyonu / Sayfa 3
Ne yazık ki, burada sunulan diğer çözümlerden bazıları, uygun bir çözümün 10 saniye içinde yarattığı aynı miktarda çıktı üretmek için üç ay süren çözümlerden bazılarıdır.
Yığın taşması üzerine kanonik bir cevap olarak makalenin bir bölümünü yeniden oluşturmak için zaman ayırmak istiyorum.
Burada en iyi performans gösteren algoritmanın algoritmamı temel aldığını ve muhtemelen bir başkasının 3. veya 4. nesil adaptasyonundan miras alındığını unutmayın. Ne yazık ki, değişiklikler performansının düşmesine neden oldu. Burada sunulan çözümümün varyasyonu, belki de for (;;)
C ile yazılmış bir sunucunun ana sonsuz döngüsüne benzeyen ve basitçe döngü kontrolü için dikkatle konumlandırılmış bir kesme ifadesine izin vermek için tasarlanmış kafa karıştırıcı ifademi anlayamadı . dizeyi katlanarak gereksiz yere bir kez çoğaltmaktan kaçının.
Bu oldukça verimli
String.prototype.repeat = function(times){
var result="";
var pattern=this;
while (times > 0) {
if (times&1)
result+=pattern;
times>>=1;
pattern+=pattern;
}
return result;
};
İyi haberler! String.prototype.repeat
olduğu şimdi JavaScript bir parçası .
"yo".repeat(2);
// returns: "yoyo"
Yöntem, Internet Explorer ve Android Webview hariç tüm büyük tarayıcılar tarafından desteklenmektedir. Güncel bir liste için bkz. MDN: String.prototype.repeat> Tarayıcı uyumluluğu .
MDN, desteksiz tarayıcılar için bir çoklu dolguya sahiptir .
String.prototype.repeat artık ES6 Standardıdır.
'abc'.repeat(3); //abcabcabc
P.Bailey'in çözümünü genişletmek :
String.prototype.repeat = function(num) {
return new Array(isNaN(num)? 1 : ++num).join(this);
}
Bu şekilde beklenmedik argüman türlerinden güvende olmalısınız:
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: Zarif fikri için jerone için kredi ++num
!
String.prototype.repeat = function(n){return new Array(isNaN(n) ? 1 : ++n).join(this);}
kullanım 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
};
bu, sınırlayıcıyı kullanarak dizeyi birkaç kez tekrarlamaktır.
İşte disfated'ın cevabında% 5-7'lik bir iyileşme.
Atlayarak döngüyü açın ve döngüden sonra count > 1
ek bir result += pattnern
concat gerçekleştirin . Bu, pattern += pattern
pahalı bir if-check kullanmak zorunda kalmadan daha önce kullanılmayan döngülerin sonunu önleyecektir . Nihai sonuç şöyle görünecektir:
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;
};
Ve işte disfated'ın keman, unrolled versiyonu için çatallandı: 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;...
:)))) Her neyse, bu teste göre ( jsperf.com/string-repeat/2 ), dizi üzerinde önerdiğiniz gibi dize konkalasyonu olan bir döngü için basit bir işlem yapmak, Array'ı kullanmaya kıyasla Chrome'da çok daha hızlı görünüyor .katılmak.
Çeşitli yöntemlerin testleri:
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));
}
}
İşte JSLint güvenli sürümü
String.prototype.repeat = function (num) {
var a = [];
a.length = num << 0 + 1;
return a.join(this);
};
Bu mümkün olduğu kadar özlü:
function repeat(s, n) { return new Array(n+1).join(s); }
Performansı da önemsiyorsanız, bu çok daha iyi bir yaklaşımdır:
function repeat(s, n) { var a=[],i=0;for(;i<n;)a[i++]=s;return a.join(''); }
Her iki seçeneğin performansını karşılaştırmak istiyorsanız, bu Fiddle ve bu Fiddle'a bakın testleri için bakın. Kendi testlerim sırasında, ikinci seçenek Firefox'ta yaklaşık 2 kat ve Chrome'da yaklaşık 4 kat daha hızlıydı!
Modern tarayıcılarda artık şunları da yapabilirsiniz:
function repeat(s,n) { return s.repeat(n) };
Bu seçenek sadece diğer seçeneklerden daha kısa değil, aynı zamanda daha da hızlı zamanda ikinci seçenekten .
Ne yazık ki, Internet explorer'ın herhangi bir sürümünde çalışmıyor. Tablodaki sayılar, yöntemi tam olarak destekleyen ilk tarayıcı sürümünü belirtir:
function repeat(pattern, count) {
for (var result = '';;) {
if (count & 1) {
result += pattern;
}
if (count >>= 1) {
pattern += pattern;
} else {
return result;
}
}
}
JSFiddle'da test edebilirsiniz . Keskin Array.join
ve benimkine karşı karşılaştırmalı olarak, kabaca konuşursak, 10 (Chrome) ila 100 (Safari) ila 200 (Firefox) arasında daha hızlıdır (tarayıcıya bağlı olarak).
ES2015
bunu gerçekleştirdi repeat()
yöntem !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/ Dize / yineleme
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
Keman: http://jsfiddle.net/3Y9v2/
function repeat(s, n){
return ((new Array(n+1)).join(s));
}
alert(repeat('R', 10));
Sadece bir bash vermek istedim ve bunu yaptım:
function ditto( s, r, c ) {
return c-- ? ditto( s, r += s, c ) : r;
}
ditto( "foo", "", 128 );
Ben çok düşündüm diyemeyiz ve muhtemelen :-) gösterir
String.prototype.ditto = function( c ) {
return --c ? this + this.ditto( c ) : this;
};
"foo".ditto( 128 );
Ve daha önce gönderilmiş bir cevap gibi - bunu biliyorum.
Ve biraz varsayılan davranışa ne dersiniz?
String.prototype.ditto = function() {
var c = Number( arguments[ 0 ] ) || 2,
r = this.valueOf();
while ( --c ) {
r += this;
}
return r;
}
"foo".ditto();
Çünkü , özyinelemesiz yöntem, çağrı yığını sınırlarına çarpmadan keyfi olarak büyük tekrarları işleyecek olsa da, çok daha yavaştır.
Kısmen kendi eğlencem için ve kısmen en basit şekilde işaret etmek için bir kediyi ciltlemenin birçok yolu olduğunu biliyorum ve duruma bağlı olarak, görünüşe göre en iyi yöntemin ideal olmaması oldukça mümkündür .
Nispeten hızlı ve sofistike bir yöntem belirli koşullar altında etkili bir şekilde çökebilir ve yanabilirken, daha yavaş, daha basit bir yöntem işi sonunda yapabilir.
Bazı yöntemler patlatır biraz daha olabilir ve eğilimli gibi olan sabit varlık dışarı ve diğer yöntemler her koşulda güzel çalışabilir, ancak böylece inşa edilir bir basitçe nasıl çalıştığını bilmiyor.
“Peki ya nasıl çalıştığını bilmezsem ?!”
Ciddi anlamda?
JavaScript en güçlü yönlerinden birini yaşar; kötü davranışlara karşı son derece toleranslıdır ve o kadar esnektir ki, sonuç elde etmek için tersine doğru eğilir, eğer koparsa herkes için daha iyi olabilirdi!
"Büyük güç büyük sorumluluk getirir" ;-)
Ama daha ciddi ve daha önemlisi, bu gibi genel sorular şeklinde muhteşemliğinin yol rağmen akıllı cevaplar başka bir şey, kişinin bilgi ve ufuklarını genişletmek eğer sonunda, el altında görev - pratik komut that use çıkan yöntemi - biraz daha az ya da biraz daha zeki olabilir önerilenden .
Bu "mükemmel" algoritmalar eğlenceli ve hepsidir, ancak "tek beden herkese uyar" , özel olarak yapılandan daha iyi olursa nadiren olacaktır.
Bu vaaz, uyku eksikliği ve geçici bir ilgi nedeniyle size getirildi. Devam edin ve kodlayın!
Birincisi, OP'nin soruları, "basit ve okunması kolay" anlamına geldiğini anladığım kısa ve verimlilikle ilgili gibi görünüyor - ki bu kesinlikle aynı şey değildir ve aynı zamanda belirli büyük veri işleme algoritmaları, temel veri işleme Javascript işlevlerini uygulamaya geldiğinizde sizi endişelendirmemelidir. Özlülük çok daha önemlidir.
İkinci olarak, André Laszlo'nun belirttiği gibi, String.repeat, ECMAScript 6'nın bir parçasıdır ve birçok popüler uygulamada zaten mevcuttur - bu yüzden en özlü String.repeat
uygulaması onu uygulamak değildir ;-)
Son olarak, ECMAScript 6 uygulamasını sunmayan ana bilgisayarları desteklemeniz gerekiyorsa, MDN'nin André Laszlo tarafından bahsedilen çoklu dolgusu kısa ve öz bir şeydir.
Yani, daha fazla uzatmadan - İşte benim özlü poli dolum:
String.prototype.repeat = String.prototype.repeat || function(n){
return n<=1 ? this : this.concat(this.repeat(n-1));
}
Evet, bu bir özyineleme. Özyinelemeleri severim - basittirler ve doğru yapılırsa anlaşılması kolaydır. Verimlilik ile ilgili olarak, dil destekliyorsa, doğru yazılırsa çok verimli olabilirler.
Testlerimden, bu yöntem Array.join
yaklaşımdan ~% 60 daha hızlı . Açıkça anlaşılan disfated'in hiçbir yerinde olmadığı halde, her ikisinden de çok daha basittir.
Benim test kurulum "Sıkı mod" (Bence bir tür TCO sağlar ) kullanarak düğüm v0.10, repeat(1000)
bir milyon kez 10 karakter dizesi çağırıyor .
Tüm bu prototip tanımlarının, dizi oluşturmalarının ve birleştirme işlemlerinin aşırı olduğunu düşünüyorsanız, ihtiyacınız olan yerde tek bir satır kodu kullanın. N kez yinelenen String S:
for (var i = 0, result = ''; i < N; i++) result += S;
Array(N + 1).join(str)
bir performans darboğazı değilse yöntemi kullanın ). İki kez kullanma şansınız en düşükse, uygun şekilde adlandırılmış bir işleve taşıyın.
Dizeleri yineleme gibi Javascript yardımcı programı işlevleri için Lodash'ı kullanın.
Lodash güzel performans ve ECMAScript uyumluluğu sağlar.
UI geliştirme için kesinlikle tavsiye ederim ve çok iyi sunucu tarafında çalışır.
Lodash kullanarak "yo" dizesini 2 kez tekrarlamak için:
> _.repeat('yo', 2)
"yoyo"
Buraya rastgele geldim ve daha önce javascript'te bir karakter tekrarlamak için bir nedenim yoktu.
Artistoex'in bunu yapma şeklinden ve disfated sonuçlarından etkilendim. Dennis'in de işaret ettiği gibi, son dize konsatının gereksiz olduğunu fark ettim.
Disfated örnekleme ile birlikte oynarken birkaç şey daha fark ettim.
Sonuçlar genellikle son çalışmayı destekleyen adil bir miktar değişiyordu ve benzer algoritmalar genellikle pozisyon için jokey yapıyordu. Değiştirdiğim şeylerden biri, çağrılar için tohum olarak JSLitmus üretilen sayısını kullanmaktı; sayısı çeşitli yöntemler için farklı üretildiğinden, bir indeks koydum. Bu, işi çok daha güvenilir hale getirdi. Sonra değişen büyüklükte dizelerin fonksiyonlara aktarılmasını sağladım. Bu, bazı algoritmaların tek karakterlerde veya daha küçük dizelerde daha iyi performans gösterdiği bazı varyasyonları engelledi. Bununla birlikte, dize büyüklüğünden bağımsız olarak ilk 3 yöntem iyi sonuç verdi.
Çatallı test seti
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();
Daha sonra Dennis'in düzeltmesini ekledim ve biraz daha fazla dışarı çıkmak için bir yol bulabileceğimi görmeye karar verdim.
Javascript işleri gerçekten optimize edemediğinden, performansı artırmanın en iyi yolu şeylerden manuel olarak kaçınmaktır. İlk 4 önemsiz sonucu döngüden çıkarırsam, 2-4 string deposundan kaçınabilir ve nihai mağazayı doğrudan sonuca yazabilirim.
// 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;
}
Bu Dennis'in fikrine göre ortalama% 1-2 iyileşme ile sonuçlandı. Ancak, farklı çalıştırmalar ve farklı tarayıcılar, bu ekstra kodun muhtemelen önceki 2 algoritma üzerinde çaba harcamaya değmeyecek kadar adil bir fark gösterecektir.
Düzenleme: Bunu çoğunlukla krom altında yaptım. Firefox ve IE genellikle Dennis'i% 10 oranında tercih eder.
Basit yöntem:
String.prototype.repeat = function(num) {
num = parseInt(num);
if (num < 0) return '';
return new Array(num + 1).join(this);
}
İnsanlar bunu gülünç derecede boşa harcıyor veya performansı boşa harcıyor. Diziler? Özyineleme? Şaka yapıyor olmalısın.
function repeat (string, times) {
var result = ''
while (times-- > 0) result += string
return result
}
Düzenle. Ben artistoex / disfated ve bir sürü başka insan tarafından yayınlanan bitwise sürümü ile karşılaştırmak için bazı basit testler yaptım. İkincisi sadece marjinal olarak daha hızlıydı, ancak büyüklük sıraları bellekte daha verimli. 'Blah' kelimesinin 1000000 tekrarı için, Düğüm işlemi basit birleştirme algoritmasıyla (yukarıda) 46 megabayta kadar yükseldi, ancak logaritmik algoritmayla sadece 5.5 megabayta çıktı. İkincisi kesinlikle gitmek için bir yoldur. Anlaşılır olması için tekrar yayınlamak:
function repeat (string, times) {
var result = ''
while (times > 0) {
if (times & 1) result += string
times >>= 1
string += string
}
return result
}
string += string
zamanın var.
ES8 ile bunun için padStart
veya kullanabilirsiniz padEnd
. Örneğin.
var str = 'cat';
var num = 23;
var size = str.length * num;
"".padStart(size, str) // outputs: 'catcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcat'