Jaka jest najlepsza lub najbardziej zwięzła metoda zwracania ciągu powtórzonego dowolną liczbę razy?
Oto mój najlepszy do tej pory strzał:
function repeat(s, n){
var a = [];
while(a.length < n){
a.push(s);
}
return a.join('');
}
javascript
string
ćwiek
źródło
źródło
Odpowiedzi:
Umieściłbym tę funkcję bezpośrednio w obiekcie String. Zamiast tworzyć tablicę, wypełniać ją i łączyć pustym znakiem, po prostu utwórz tablicę o odpowiedniej długości i połącz ją z pożądanym łańcuchem. Ten sam wynik, mniej procesów!
źródło
String.repeat = function(string, num){ return new Array(parseInt(num) + 1).join(string); };
. Nazwij to tak:String.repeat('/\', 20)
Testowałem wydajność wszystkich proponowanych podejść.
Oto najszybszy wariant , jaki mam.
Lub jako samodzielna funkcja:
Opiera się na algorytmie artistoex . To jest naprawdę szybkie. A im większy
count
, tym szybciej idzie w porównaniu z tradycyjnymnew Array(count + 1).join(string)
podejściem.Zmieniłem tylko 2 rzeczy:
pattern = this
się zpattern = this.valueOf()
(bezbarwnych oczywistym typu konwersji);if (count < 1)
kontrolę od prototypejs na górze funkcji, aby wykluczyć niepotrzebne działania w tym przypadku.UPD
Stworzył małą wydajność testowania zabaw tutaj dla tych, którzy interesują.
zmienna
count
~ 0 .. 100:stała
count
= 1024:Skorzystaj z niego i jeszcze szybciej, jeśli możesz :)
źródło
count < 1
sprawa jest naprawdę niepotrzebną optymalizacją.Problem ten jest dobrze znanym / „klasycznym” problemem optymalizacji dla JavaScript, spowodowanym faktem, że ciągi JavaScript są „niezmienne”, a dodanie przez połączenie nawet jednego znaku z ciągu wymaga utworzenia, w tym alokacji pamięci i skopiowania do , cały nowy ciąg.
Niestety, przyjęta odpowiedź na tej stronie jest błędna, gdzie „zła” oznacza współczynnik wydajności 3x dla prostych ciągów jednoznakowych i 8x-97x dla krótkich ciągów powtarzanych więcej razy, do 300x dla powtarzania zdań i nieskończenie zły, gdy przyjmowanie granicy współczynników złożoności algorytmów
n
do nieskończoności. Jest też inna odpowiedź na tej stronie, która jest prawie słuszna (oparta na jednej z wielu generacji i odmian prawidłowego rozwiązania krążących w Internecie w ciągu ostatnich 13 lat). Jednak w tym „prawie właściwym” rozwiązaniu brakuje kluczowego punktu prawidłowego algorytmu, powodując obniżenie wydajności o 50%.Wyniki wydajności JS dla zaakceptowanej odpowiedzi, drugiej najbardziej wydajnej odpowiedzi (na podstawie zdegradowanej wersji oryginalnego algorytmu w tej odpowiedzi) i tej odpowiedzi przy użyciu mojego algorytmu utworzonego 13 lat temu
~ Październik 2000 Opublikowałem algorytm tego dokładnie problemu, który został szeroko dostosowany, zmodyfikowany, a następnie ostatecznie źle zrozumiany i zapomniany. Aby temu zaradzić, w sierpniu 2008 roku opublikowałem artykuł http://www.webreference.com/programming/javascript/jkm3/3.html wyjaśniający algorytm i wykorzystujący go jako przykład prostych ogólnych optymalizacji JavaScript. Do tej pory Web Reference wyczyściło moje dane kontaktowe, a nawet moje nazwisko z tego artykułu. I jeszcze raz algorytm został szeroko dostosowany, zmodyfikowany, a następnie źle zrozumiany i w dużej mierze zapomniany.
W ciągu dwóch miesięcy po opublikowaniu tego artykułu to samo pytanie zostało wysłane do Stack Overflow i leciało pod moim radarem aż do chwili, kiedy najwyraźniej pierwotny algorytm tego problemu został ponownie zapomniany. Najlepszym rozwiązaniem dostępnym na tej stronie przepełnienia stosu jest zmodyfikowana wersja mojego rozwiązania, prawdopodobnie rozdzielona przez kilka pokoleń. Niestety modyfikacje zepsuły optymalność rozwiązania. W rzeczywistości, zmieniając strukturę pętli z mojego oryginału, zmodyfikowane rozwiązanie wykonuje całkowicie niepotrzebny dodatkowy krok wykładniczego powielania (łącząc w ten sposób największy ciąg użyty we właściwej odpowiedzi ze sobą dodatkowy czas, a następnie odrzucając go).
Poniżej omówiono niektóre optymalizacje JavaScript związane z wszystkimi odpowiedziami na ten problem i dla wszystkich.
Technika: Unikaj odwołań do obiektów lub właściwości obiektów
Aby zilustrować działanie tej techniki, używamy rzeczywistej funkcji JavaScript, która tworzy ciągi o dowolnej potrzebnej długości. I jak zobaczymy, można dodać więcej optymalizacji!
Funkcją taką jak tutaj użyta jest tworzenie wypełnienia w celu wyrównania kolumn tekstu, formatowania pieniędzy lub wypełniania danych bloku do granicy. Funkcja generowania tekstu umożliwia także wprowadzanie zmiennych długości w celu testowania dowolnej innej funkcji, która działa na tekście. Ta funkcja jest jednym z ważnych elementów modułu przetwarzania tekstu JavaScript.
W miarę postępów będziemy omawiać jeszcze dwie najważniejsze techniki optymalizacji, jednocześnie rozwijając oryginalny kod w zoptymalizowany algorytm do tworzenia łańcuchów. Ostateczny wynik to przemysłowa, wysokowydajna funkcja, z której korzystałem wszędzie - wyrównanie cen produktów i sum w formularzach zamówień JavaScript, formatowanie danych oraz formatowanie wiadomości e-mail / SMS i wiele innych zastosowań.
Oryginalny kod do tworzenia ciągów
stringFill1()
Składnia jest tutaj jasna. Jak widać, użyliśmy już lokalnych zmiennych funkcji, zanim przejdziemy do dalszych optymalizacji.
Pamiętaj, że
s.length
w kodzie jest jedno niewinne odniesienie do właściwości obiektu, które ma negatywny wpływ na jego wydajność. Co gorsza, użycie tej właściwości obiektu zmniejsza prostotę programu, przyjmując założenie, że czytelnik wie o właściwościach ciągów znaków JavaScript.Użycie tej właściwości obiektu niszczy ogólność programu komputerowego. Program zakłada, że
x
musi to być ciąg długości jeden. Ogranicza to zastosowaniestringFill1()
funkcji do czegokolwiek poza powtarzaniem pojedynczych znaków. Nawet pojedyncze znaki nie mogą być użyte, jeśli zawierają wiele bajtów, takich jak encja HTML
.Najgorszym problemem spowodowanym tym niepotrzebnym użyciem właściwości obiektu jest to, że funkcja tworzy nieskończoną pętlę, jeśli jest testowana na pustym ciągu wejściowym
x
. Aby sprawdzić ogólność, zastosuj program do jak najmniejszej ilości danych wejściowych. Program, który ulega awarii, gdy zostanie wyświetlony monit o przekroczenie ilości dostępnej pamięci, ma usprawiedliwienie. Program taki jak ten, który ulega awarii, gdy zostanie poproszony o nic nie produkować, jest niedopuszczalny. Czasami ładny kod jest trującym kodem.Prostota może być niejednoznacznym celem programowania komputerowego, ale generalnie tak nie jest. Gdy programowi brakuje jakiegokolwiek rozsądnego poziomu ogólności, nie można powiedzieć: „Program jest wystarczająco dobry, o ile to możliwe”. Jak widać, użycie tej
string.length
właściwości uniemożliwia temu programowi działanie w ustawieniach ogólnych, aw rzeczywistości niepoprawny program jest gotowy do spowodowania awarii przeglądarki lub systemu.Czy istnieje sposób na poprawę wydajności tego JavaScript oraz rozwiązanie tych dwóch poważnych problemów?
Oczywiście. Po prostu użyj liczb całkowitych.
Zoptymalizowany kod do tworzenia ciągów
stringFill2()
Kod czasowy do porównania
stringFill1()
istringFill2()
Dotychczasowy sukces
stringFill2()
stringFill1()
zajmuje 47.297 mikrosekund (milionowych części sekundy), aby wypełnić łańcuch o długości 100 bajtów, istringFill2()
zajmuje 27,68 mikrosekund, aby zrobić to samo. To prawie dwukrotne zwiększenie wydajności dzięki uniknięciu odwołania do właściwości obiektu.Technika: Unikaj dodawania krótkich łańcuchów do długich łańcuchów
Nasz poprzedni wynik wyglądał dobrze - w rzeczywistości bardzo dobry. Udoskonalona funkcja
stringFill2()
jest znacznie szybsza dzięki zastosowaniu naszych pierwszych dwóch optymalizacji. Czy uwierzyłbyś, gdybym ci powiedział, że można go poprawić wiele razy szybciej niż teraz?Tak, możemy osiągnąć ten cel. W tej chwili musimy wyjaśnić, w jaki sposób unikamy dodawania krótkich łańcuchów do długich łańcuchów.
Krótkoterminowe zachowanie wydaje się być całkiem dobre w porównaniu z naszą pierwotną funkcją. Informatycy lubią analizować „asymptotyczne zachowanie” algorytmu funkcji lub programu komputerowego, co oznacza badanie jego długoterminowego zachowania poprzez testowanie go przy użyciu większych danych wejściowych. Czasami bez wykonywania dalszych testów nigdy nie zdajemy sobie sprawy, w jaki sposób można poprawić program komputerowy. Aby zobaczyć, co się stanie, utworzymy ciąg 200-bajtowy.
Problem, który się pojawia
stringFill2()
Korzystając z naszej funkcji pomiaru czasu, okazuje się, że czas wzrasta do 62,54 mikrosekundy dla ciągu 200-bajtowego, w porównaniu do 27,68 dla ciągu 100-bajtowego. Wydaje się, że czas należy podwoić, aby wykonać dwukrotnie więcej pracy, ale zamiast tego jest on trzykrotnie lub czterokrotnie. Z doświadczenia w programowaniu wynik ten wydaje się dziwny, ponieważ jeśli już, funkcja powinna być nieco szybsza, ponieważ praca jest wykonywana bardziej wydajnie (200 bajtów na wywołanie funkcji zamiast 100 bajtów na wywołanie funkcji). Ten problem dotyczy podstępnej właściwości ciągów JavaScript: ciągi JavaScript są „niezmienne”.
Niezmienny oznacza, że nie można zmienić łańcucha po jego utworzeniu. Dodając jeden bajt na raz, nie zużywamy jeszcze jednego bajtu wysiłku. W rzeczywistości odtwarzamy cały ciąg plus jeszcze jeden bajt.
W efekcie, aby dodać jeszcze jeden bajt do ciągu 100-bajtowego, potrzeba 101 bajtów pracy. Przeanalizujmy krótko koszt obliczeniowy tworzenia ciągu
N
bajtów. Koszt dodania pierwszego bajtu to 1 jednostka wysiłku obliczeniowego. Koszt dodania drugiego bajtu to nie jedna jednostka, ale 2 jednostki (skopiowanie pierwszego bajtu do nowego obiektu ciągu, a także dodanie drugiego bajtu). Trzeci bajt wymaga kosztu 3 jednostek itp.C(N) = 1 + 2 + 3 + ... + N = N(N+1)/2 = O(N^2)
. SymbolO(N^2)
jest wymawiany jako Big O z N do kwadratu, a to oznacza, że koszt obliczeniowy w długim okresie jest proporcjonalny do kwadratu długości łańcucha. Stworzenie 100 znaków wymaga 10 000 jednostek pracy, a stworzenie 200 znaków wymaga 40 000 jednostek pracy.Dlatego utworzenie ponad 200 znaków zajęło ponad dwa razy więcej czasu. W rzeczywistości powinno to zająć cztery razy dłużej. Nasze doświadczenie w programowaniu było prawidłowe, ponieważ praca jest wykonywana nieco bardziej wydajnie dla dłuższych ciągów, a zatem zajęło to tylko około trzy razy dłużej. Gdy narzut wywołania funkcji stanie się nieistotny, jeśli chodzi o długość tworzonego ciągu, utworzenie tego ciągu będzie czterokrotnie więcej czasu.
(Uwaga historyczna: Ta analiza niekoniecznie dotyczy łańcuchów w kodzie źródłowym, na przykład
html = 'abcd\n' + 'efgh\n' + ... + 'xyz.\n'
, ponieważ kompilator kodu źródłowego JavaScript może łączyć łańcuchy razem przed przekształceniem ich w obiekt łańcucha JavaScript. Kilka lat temu implementacja KJS JavaScript zawiesza się lub ulega awarii podczas ładowania długich ciągów kodu źródłowego połączonych znakami plus. Ponieważ czas obliczeniowyO(N^2)
nie był trudny, tworzenie stron internetowych, które przeciążały przeglądarkę Konqueror lub Safari, które korzystały z rdzenia silnika KJS JavaScript. natknąłem się na ten problem, gdy opracowywałem język znaczników i parser języka znaczników JavaScript, a następnie odkryłem, co było przyczyną problemu, gdy napisałem skrypt dla JavaScript Includes).Oczywiście ta szybka degradacja wydajności jest ogromnym problemem. Jak sobie z tym poradzić, skoro nie możemy zmienić sposobu obsługi napisów przez JavaScript jako obiektów niezmiennych? Rozwiązaniem jest użycie algorytmu, który odtworzy ciąg tak mało, jak to możliwe.
Aby to wyjaśnić, naszym celem jest unikanie dodawania krótkich łańcuchów do długich łańcuchów, ponieważ aby dodać krótki łańcuch, cały długi łańcuch również musi zostać zduplikowany.
Jak działa algorytm, aby uniknąć dodawania krótkich łańcuchów do długich łańcuchów
Oto dobry sposób na zmniejszenie liczby razy, gdy tworzone są nowe obiekty łańcuchowe. Połącz dłuższe ciągi razem, aby do wyjścia dodawano więcej niż jeden bajt na raz.
Na przykład, aby utworzyć ciąg długości
N = 9
:Wykonanie tego wymagało utworzenia ciągu o długości 1, utworzenia ciągu o długości 2, utworzenia ciągu o długości 4, utworzenia ciągu o długości 8 i wreszcie utworzenia ciągu o długości 9. Ile zaoszczędziliśmy na kosztach?
Stary koszt
C(9) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 9 = 45
.Nowy koszt
C(9) = 1 + 2 + 4 + 8 + 9 = 24
.Zauważ, że musieliśmy dodać ciąg o długości 1 do ciągu o długości 0, następnie ciąg o długości 1 do ciągu o długości 1, a następnie ciąg o długości 2 do ciągu o długości 2, a następnie ciąg o długości 4 na ciąg o długości 4, następnie ciąg o długości 8 na ciąg o długości 1, w celu uzyskania ciągu o długości 9. To, co robimy, można streścić, unikając dodawania krótkich ciągów do długich ciągów lub w inny sposób słowa, próbując połączyć ze sobą ciągi o równej lub prawie równej długości.
Dla starego kosztu obliczeniowego znaleźliśmy wzór
N(N+1)/2
. Czy istnieje wzór na nowy koszt? Tak, ale to skomplikowane. Ważną rzeczą jest to, że tak jestO(N)
, dlatego podwojenie długości struny w przybliżeniu podwoi ilość pracy, zamiast ją czterokrotnie.Kod implementujący ten nowy pomysł jest prawie tak skomplikowany, jak formuła kosztu obliczeniowego. Kiedy ją czytasz, pamiętaj, że
>>= 1
oznacza to przesunięcie w prawo o 1 bajt. Więc jeślin = 10011
jest liczbą binarną, ton >>= 1
daje wartośćn = 1001
.Inną częścią kodu, której możesz nie rozpoznać, jest zapis bitowy i operator
&
. Wyrażenien & 1
ocenia true, jeśli ostatnia cyfra binarnan
to 1, i false, jeśli ostatnia cyfra binarnan
wynosi 0.Nowa wysoce wydajna
stringFill3()
funkcjaDla niewprawnego oka wygląda to brzydko, ale jego wydajność jest niczym innym, jak uroczym.
Zobaczmy, jak dobrze działa ta funkcja. Po zobaczeniu wyników prawdopodobnie nigdy nie zapomnisz różnicy między
O(N^2)
algorytmem aO(N)
algorytmem.stringFill1()
zajmuje 88,7 mikrosekund (milionowych części sekundy), aby utworzyć ciąg 200-bajtowy,stringFill2()
zajmuje 62,54 istringFill3()
zajmuje tylko 4,608. Co sprawiło, że ten algorytm był o wiele lepszy? Wszystkie funkcje wykorzystały lokalne zmienne funkcyjne, ale skorzystanie z drugiej i trzeciej techniki optymalizacji dodało dwudziestokrotną poprawę wydajnościstringFill3()
.Głębsza analiza
Co sprawia, że ta konkretna funkcja wysadza konkurencję z wody?
Jak już wspomniano, dlatego, że obie te funkcje,
stringFill1()
istringFill2()
uruchomić tak powoli, że ciągi są niezmienne JavaScript. Pamięci nie można ponownie przydzielić, aby umożliwić dołączenie jednego bajtu naraz do danych ciągów przechowywanych przez JavaScript. Za każdym razem, gdy na końcu łańcucha dodawany jest jeszcze jeden bajt, cały łańcuch jest generowany od początku do końca.Tak więc, aby poprawić wydajność skryptu, należy wstępnie obliczyć łańcuchy o większej długości, łącząc dwa łańcuchy przed czasem, a następnie rekurencyjnie budując pożądaną długość łańcucha.
Na przykład, aby utworzyć 16-literowy ciąg bajtów, najpierw należy wstępnie obliczyć ciąg dwóch bajtów. Następnie dwubajtowy ciąg znaków zostałby ponownie wykorzystany do wstępnego obliczenia czterobajtowego ciągu. Następnie czterobajtowy ciąg znaków zostanie ponownie wykorzystany do wstępnego obliczenia ośmiobajtowego ciągu. Wreszcie dwa ośmiobajtowe ciągi znaków zostaną ponownie wykorzystane do utworzenia pożądanego nowego ciągu 16 bajtów. W sumie musiały zostać utworzone cztery nowe ciągi, jeden o długości 2, jeden o długości 4, jeden o długości 8 i jeden o długości 16. Całkowity koszt to 2 + 4 + 8 + 16 = 30.
Na dłuższą metę wydajność tę można obliczyć, dodając w odwrotnej kolejności i używając szeregu geometrycznego rozpoczynającego się od pierwszego członu a1 = N i mającego wspólny stosunek r = 1/2. Suma szeregu geometrycznego jest podana przez
a_1 / (1-r) = 2N
.Jest to bardziej wydajne niż dodawanie jednego znaku w celu utworzenia nowego ciągu o długości 2, tworzenia nowego ciągu o długości 3, 4, 5 itd. Do 16. Poprzedni algorytm używał tego procesu dodawania pojedynczego bajtu naraz , a całkowity koszt wyniósłby
n (n + 1) / 2 = 16 (17) / 2 = 8 (17) = 136
.Oczywiście 136 jest znacznie większą liczbą niż 30, więc poprzedni algorytm zajmuje dużo, dużo więcej czasu, aby zbudować ciąg.
Aby porównać te dwie metody, możesz zobaczyć, o ile szybciej algorytm rekurencyjny (zwany także „dziel i rządź”) ma ciąg długości 123.457. Na moim komputerze FreeBSD ten algorytm, zaimplementowany w
stringFill3()
funkcji, tworzy ciąg w 0,001058 sekund, podczas gdy oryginalnastringFill1()
funkcja tworzy ciąg w 0,0808 sekund. Nowa funkcja jest 76 razy szybsza.Różnica w wydajności rośnie wraz ze wzrostem długości łańcucha. W granicy, gdy tworzone są coraz większe ciągi, oryginalna funkcja zachowuje się mniej więcej tak jak
C1
(stałe) czasyN^2
, a nowa funkcja zachowuje się jakC2
(stała) razyN
.Na podstawie naszego eksperymentu możemy określić wartość
C1
bytuC1 = 0.0808 / (123457)2 = .00000000000530126997
i wartośćC2
bytuC2 = 0.001058 / 123457 = .00000000856978543136
. W ciągu 10 sekund nowa funkcja może utworzyć ciąg zawierający 1168 890 359 znaków. Aby utworzyć ten sam ciąg, stara funkcja potrzebowałaby 7 218 384 sekund.To prawie trzy miesiące w porównaniu do dziesięciu sekund!
Odpowiadam tylko (kilka lat z opóźnieniem), ponieważ moje oryginalne rozwiązanie tego problemu krąży w Internecie od ponad 10 lat i najwyraźniej wciąż jest słabo rozumiane przez niewielu, którzy go pamiętają. Pomyślałem, że pisząc tutaj o tym artykuł, pomogę:
Optymalizacje wydajności dla szybkiego JavaScript / strona 3
Niestety, niektóre z innych przedstawionych tutaj rozwiązań to nadal niektóre z tych, które zajęłyby trzy miesiące, aby uzyskać taką samą moc wyjściową, jaką tworzy właściwe rozwiązanie w 10 sekund.
Chcę poświęcić trochę czasu na odtworzenie tutaj tego artykułu jako kanonicznej odpowiedzi na temat przepełnienia stosu.
Zauważ, że najskuteczniejszy algorytm tutaj jest wyraźnie oparty na moim algorytmie i prawdopodobnie został odziedziczony z adaptacji trzeciej lub czwartej generacji innej osoby. Niestety modyfikacje spowodowały zmniejszenie jego wydajności. Przedstawiona tutaj odmiana mojego rozwiązania może nie rozumieć mojego mylącego
for (;;)
wyrażenia, które wygląda jak główna nieskończona pętla serwera napisanego w C i które zostało po prostu zaprojektowane, aby umożliwić dokładnie ustawione polecenie break do sterowania pętlą, najbardziej kompaktowy sposób unikaj wykładniczego powtarzania ciągu o jeden dodatkowy niepotrzebny czas.źródło
Ten jest dość wydajny
źródło
Dobre wieści!
String.prototype.repeat
jest teraz częścią JavaScript .Ta metoda jest obsługiwana przez wszystkie główne przeglądarki, z wyjątkiem Internet Explorera i Androida Webview. Aby uzyskać aktualną listę, zobacz MDN: String.prototype.repeat> Kompatybilność z przeglądarkami .
MDN ma polifill dla przeglądarek bez wsparcia.
źródło
String.prototype.repeat ma teraz standard ES6.
źródło
Rozszerzanie rozwiązania P.Baileya :
W ten sposób powinieneś być bezpieczny przed nieoczekiwanymi typami argumentów:
EDYCJA: Podziękowania dla jerone za jego elegancki
++num
pomysł!źródło
String.prototype.repeat = function(n){return new Array(isNaN(n) ? 1 : ++n).join(this);}
Posługiwać się
Array(N+1).join("string_to_repeat")
źródło
w ten sposób kilka razy powtarza się ciąg za pomocą ogranicznika.
źródło
Oto 5-7% poprawa w stosunku do zdesperowanych.
Rozwiń pętlę, zatrzymując się przy niej
count > 1
i wykonaj dodatkowąresult += pattnern
konkat po pętli. Pozwoli to uniknąć pętli, które nie były poprzednio nieużywanepattern += pattern
bez konieczności korzystania z drogiego sprawdzania zgodności. Ostateczny wynik wyglądałby następująco:Oto rozwidlone skrzypce rozwidlone dla wersji rozwijanej: http://jsfiddle.net/wsdfg/
źródło
źródło
var r=s; for (var a=1;...
:)))) W każdym razie zgodnie z tym testem ( jsperf.com/string-repeat/2 ) wykonanie prostej pętli for z układaniem łańcuchów znaków, jak sugerujesz, wydaje się być znacznie szybsze w Chrome w porównaniu do korzystania z Array .Przystąp.Testy różnych metod:
źródło
Oto bezpieczna wersja JSLint
źródło
Dla wszystkich przeglądarek
Jest to tak zwięzłe, jak to możliwe:
Jeśli zależy Ci również na wydajności, jest to znacznie lepsze podejście:
Jeśli chcesz porównać wydajność obu opcji, zobacz ten Fiddle i ten Fiddle do testów porównawczych. Podczas moich własnych testów druga opcja była około 2 razy szybsza w Firefoksie i około 4 razy szybsza w Chrome!
Tylko dla współczesnych przeglądarek:
W nowoczesnych przeglądarkach możesz teraz to zrobić:
Ta opcja jest nie tylko krótsza niż obie inne opcje, ale jest nawet szybsza niż druga opcja.
Niestety nie działa w żadnej wersji Internet Explorera. Liczby w tabeli określają pierwszą wersję przeglądarki, która w pełni obsługuje tę metodę:
źródło
Możesz to przetestować w JSFiddle . Porównywany z hacky
Array.join
i mój jest, z grubsza, 10 (Chrome) do 100 (Safari) do 200 (Firefox) razy szybciej (w zależności od przeglądarki).źródło
Kolejna funkcja powtarzania:
źródło
ES2015
zrealizowano tęrepeat()
metodę!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/ Ciąg / powtórz
http://www.w3schools.com/jsref/jsref_repeat.asp
źródło
Może to być najmniejszy cykliczny:
źródło
Fiddle: http://jsfiddle.net/3Y9v2/
źródło
Prosta rekurencyjna konkatenacja
Chciałem tylko dać temu spokój i zrobiłem to:
Nie mogę powiedzieć, że zastanowiłem się nad tym i prawdopodobnie pokazuje :-)
To jest prawdopodobnie lepsze
I to bardzo przypomina odpowiedź już opublikowaną - wiem o tym.
Ale po co w ogóle być rekurencyjnym?
A może trochę domyślne zachowanie?
Ponieważ chociaż nierekurencyjna metoda poradzi sobie z dowolnie dużymi powtórzeniami bez przekraczania limitów stosów wywołań, jest znacznie wolniejsza.
Dlaczego zawracałem sobie głowę dodawaniem kolejnych metod, które nie są w połowie tak sprytne, jak te już opublikowane?
Częściowo dla własnej rozrywki, a częściowo w najprostszy sposób wiem, że istnieje wiele sposobów na skórowanie kota, a w zależności od sytuacji całkiem możliwe, że najwyraźniej najlepsza metoda nie jest idealna.
Stosunkowo szybka i wyrafinowana metoda może w pewnych okolicznościach skutecznie ulec awarii i spaleniu, podczas gdy wolniejsza, prostsza metoda może ostatecznie zakończyć pracę.
Niektóre metody mogą być trochę więcej niż wyczyny i jako takie są podatne na stałe z istnieniem, a inne metody mogą działać pięknie w każdych warunkach, ale są tak skonstruowane, że jeden po prostu nie ma pojęcia jak to działa.
„A co jeśli nie wiem, jak to działa ?!”
Poważnie?
JavaScript ma jedną z największych zalet; jest wysoce tolerancyjny na złe zachowanie i jest tak elastyczny, że pochyla się do tyłu, aby zwrócić wyniki, gdy mogłoby być lepsze dla wszystkich, gdyby pękło!
„Z wielką mocą wiąże się wielka odpowiedzialność” ;-)
Ale co ważniejsze i co ważniejsze, chociaż takie ogólne pytania prowadzą do niesamowitości w postaci sprytnych odpowiedzi, że jeśli nic więcej, poszerzają swoją wiedzę i horyzonty, w końcu zadanie, o które chodzi - praktyczny skrypt, który wykorzystuje wynikową metodę - może wymagać trochę mniej lub trochę sprytniej, niż sugeruje.
Te „idealne” algorytmy są fajne i wszystkie, ale „jeden rozmiar dla wszystkich” rzadko, jeśli w ogóle, będzie lepszy niż na zamówienie.
To kazanie zostało przedstawione dzięki uprzejmości i brakowi zainteresowania. Idź naprzód i koduj!
źródło
Po pierwsze, wydaje się, że pytania PO dotyczą zwięzłości - co rozumiem jako „proste i łatwe do odczytania”, podczas gdy większość odpowiedzi wydaje się dotyczyć wydajności - co oczywiście nie jest tym samym, i myślę, że chyba, że zastosuje się jakieś bardzo specyficzne algorytmy do manipulowania dużymi danymi, nie powinny cię martwić, kiedy zaczniesz implementować podstawowe funkcje JavaScript do manipulacji danymi. Zwięzłość jest o wiele ważniejsza.
Po drugie, jak zauważył André Laszlo, String.repeat jest częścią ECMAScript 6 i jest już dostępny w kilku popularnych implementacjach - więc najbardziej zwięzłą implementacją
String.repeat
jest nie implementowanie go ;-)Wreszcie, jeśli potrzebujesz obsługiwać hosty, które nie oferują implementacji ECMAScript 6, wypełnienie MDN wspomniane przez André Laszlo nie jest zwięzłe.
Bez zbędnych ceregieli - oto mój zwięzły wypełnienie:
Tak, to jest rekurencja. Lubię rekurencje - są proste, a jeśli wykonane poprawnie, łatwo je zrozumieć. Jeśli chodzi o wydajność, jeśli język go obsługuje, mogą być bardzo wydajne, jeśli zostaną poprawnie napisane.
Z moich testów wynika, że ta metoda jest ~ 60% szybsza niż
Array.join
podejście. Chociaż oczywiście nie jest to implementacja rozproszona, jest znacznie prostsza niż obie.Moja konfiguracja testowa to węzeł v0.10, używając „trybu ścisłego” (myślę, że umożliwia on pewien rodzaj TCO ), wzywając
repeat(1000)
milion znaków w ciągu 10 znaków.źródło
Jeśli uważasz, że wszystkie te prototypowe definicje, tworzenie tablic i operacje łączenia są przesadzone, po prostu użyj kodu jednej linii tam, gdzie jest to potrzebne. Ciąg S powtarzający się N razy:
źródło
Array(N + 1).join(str)
metody, jeśli nie jest to wąskie gardło wydajności). Jeśli istnieje najmniejsza szansa, że użyjesz go dwa razy, przenieś go do odpowiednio nazwanej funkcji.Użyj funkcji narzędzia Lodash dla Javascript, np. Powtarzania ciągów.
Lodash zapewnia niezłą wydajność i zgodność z ECMAScript.
Bardzo polecam go do tworzenia interfejsu użytkownika i działa również po stronie serwera.
Oto jak powtórzyć ciąg „yo” 2 razy za pomocą Lodash:
źródło
Rozwiązanie rekurencyjne wykorzystujące dzielenie i podbijanie
źródło
Przybyłem tu losowo i nigdy wcześniej nie miałem powodu, aby powtarzać znak w javascript.
Byłem pod wrażeniem sposobu, w jaki artistoex to zrobił, i rezultatów rozproszonych. Zauważyłem, że ostatni konkord struny był niepotrzebny, jak zauważył Dennis.
Zauważyłem jeszcze kilka rzeczy podczas zabawy z rozproszonym próbkowaniem.
Wyniki różniły się dość często, co sprzyjało ostatniemu biegowi, a podobne algorytmy często podważałyby pozycję. Jedną z rzeczy, które zmieniłem, było użycie generowanego przez JSLitmus licznika jako źródła dla połączeń; ponieważ liczba generowana była różnie dla różnych metod, wstawiłem indeks. To sprawiło, że rzecz była o wiele bardziej niezawodna. Następnie sprawdziłem, czy do funkcji zostały przekazane łańcuchy o różnych rozmiarach. Zapobiegło to niektórym odmianom, które widziałem, gdzie niektóre algorytmy radziły sobie lepiej na pojedynczych znakach lub mniejszych ciągach. Jednak wszystkie 3 najlepsze metody wypadły dobrze, niezależnie od wielkości łańcucha.
Zestaw testowy rozwidlony
http://jsfiddle.net/schmide/fCqp3/134/
Następnie załączyłem poprawkę Dennisa i zdecydowałem, czy mogę znaleźć sposób, aby trochę więcej wypróbować.
Ponieważ JavaScript nie może tak naprawdę optymalizować rzeczy, najlepszym sposobem na poprawę wydajności jest ręczne unikanie rzeczy. Gdybym wyjął z pętli pierwsze 4 trywialne wyniki, mogłem uniknąć 2-4 ciągów znaków i zapisać końcowy sklep bezpośrednio do wyniku.
Spowodowało to średnio 1-2% poprawę w stosunku do poprawki Dennisa. Jednak różne przebiegi i różne przeglądarki wykazują wystarczającą wariancję, aby ten dodatkowy kod prawdopodobnie nie był wart wysiłku w porównaniu z 2 poprzednimi algorytmami.
Wykres
Edycja: Zrobiłem to głównie pod chromem. Firefox i IE często faworyzują Dennisa o kilka%.
źródło
Prosta metoda:
źródło
Ludzie komplikują to w absurdalny sposób lub marnują wydajność. Tablice? Rekurencja? Chyba sobie żartujesz.
Edytować. Przeprowadziłem kilka prostych testów w celu porównania z wersją bitową opublikowaną przez artistoex / disfated i grupę innych osób. Ten ostatni był tylko nieznacznie szybszy, ale rzędy wielkości bardziej wydajne pod względem pamięci. W przypadku 1000000 powtórzeń słowa „bla” proces Node wzrósł do 46 megabajtów za pomocą prostego algorytmu konkatenacji (powyżej), ale tylko 5,5 megabajtów za pomocą algorytmu logarytmicznego. To drugie jest zdecydowanie właściwą drogą. Przesłanie go dla zachowania jasności:
źródło
string += string
połowę czasu.Łączenie ciągów znaków na podstawie liczby.
Mam nadzieję, że to pomaga!
źródło
Z ES8 możesz również użyć
padStart
lubpadEnd
do tego. na przykład.źródło