Який найкращий чи найкоротший спосіб повернення рядка, повтореного в довільній кількості разів?
Наступний мій найкращий знімок досі:
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('');
}
Відповіді:
Зауважте новим читачам: ця відповідь стара і не дуже практична - вона просто "розумна", оскільки вона використовує масив масиву, щоб виконати String речі. Коли я писав "менше процесу", я напевно мав на увазі "менше коду", тому що, як зазначали інші у наступних відповідях, він виконує функцію "свиня". Тому не використовуйте його, якщо швидкість має значення для вас.
Я б поставив цю функцію безпосередньо на об'єкт 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)
перевірку від прототипу до верхньої частини функції, щоб виключити непотрібні дії в цьому випадку.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 загального призначення. На даний момент веб-довідник очистив мою контактну інформацію і навіть моє ім’я з цієї статті. І ще раз, алгоритм був широко адаптований, модифікований, потім погано зрозумілий і значною мірою забутий.
Оригінальний алгоритм повторення / множення рядків JavaScript Джозефа Майєрса, близько Y2K як функція множення тексту в Text.js; опубліковано у серпні 2008 р. у цій формі за веб-посиланням: 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 і пролетіло під моїм радаром дотепер, коли, мабуть, оригінальний алгоритм цієї проблеми знову був забутий. Найкраще рішення на цій сторінці переповнення стека - це модифікована версія мого рішення, можливо, розділена на кілька поколінь. На жаль, модифікації зруйнували оптимальність рішення. Насправді, змінюючи структуру циклу з мого оригіналу, модифіковане рішення виконує абсолютно непотрібний додатковий крок експоненціального дублювання (таким чином, приєднуючи найбільший рядок, використовуваний у правильній відповіді, із собою додатковий час, а потім відкидаючи його).
Нижче йде обговорення деяких оптимізацій 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()
Для заповнення 100-байтового рядка потрібно 47,297 мікросекунд (мільйонних секунд), і stringFill2()
те ж саме потрібно 27,68 мікросекунд . Це майже подвоєння продуктивності, уникаючи посилання на властивість об'єкта.
Наш попередній результат виглядав непогано - насправді дуже добре. Удосконалена функція stringFill2()
набагато швидша за рахунок використання наших перших двох оптимізацій. Чи повірите ви, якби я сказав вам, що це можна вдосконалити в багато разів швидше, ніж зараз?
Так, ми можемо досягти цієї мети. Зараз нам потрібно пояснити, як ми уникаємо додавання коротких рядків до довгих рядків.
Короткочасна поведінка виявляється досить хорошою, порівняно з нашою початковою функцією. Комп'ютерні фахівці люблять аналізувати "асимптотичну поведінку" функції або алгоритму комп'ютерної програми, що означає вивчити її довгострокову поведінку, перевіряючи її з більшими вхідними даними. Іноді, не роблячи подальших тестів, ніколи не стає відомо про шляхи вдосконалення комп'ютерної програми. Щоб побачити, що станеться, ми збираємося створити 200-байтовий рядок.
Проблема, яка виявляється в stringFill2()
Використовуючи нашу функцію синхронізації, ми виявляємо, що час збільшується до 62,54 мікросекунди для 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)
вимовляється Big O з N квадрата, і це означає, що обчислювальна вартість в перспективі пропорційна квадрату довжини струни. Для створення 100 символів потрібно 10000 одиниць роботи, а для створення 200 символів потрібно 40 000 одиниць роботи.
Ось чому для створення 200 символів, ніж 100 символів, знадобилося більше ніж удвічі. Насправді це повинно було пройти в чотири рази довше. Наш досвід програмування був правильним, оскільки робота виконується дещо ефективніше для довших рядків, а значить, це зайняло лише близько трьох разів. Як тільки накладні витрати функціонального виклику стають незначними щодо того, як довго буде створена нитка, для створення рядка вдвічі довше буде в чотири рази більше часу.
(Історична примітка: Цей аналіз не обов'язково застосовується до рядків у вихідному коді, наприклад html = 'abcd\n' + 'efgh\n' + ... + 'xyz.\n'
, оскільки компілятор вихідного коду JavaScript може об'єднати рядки разом, перш ніж перетворити їх у об'єкт JavaScript-рядка. Ще кілька років тому реалізація KJS JavaScript зависає або збивається під час завантаження довгих рядків вихідного коду, з'єднаних знаками плюс. Оскільки для обчислювального часу O(N^2)
не важко було зробити веб-сторінки, які перевантажували веб-браузер Konqueror або Safari, які використовували ядро двигуна KJS JavaScript. Перший я зіткнувся з цим питанням, коли я розробляв мову розмітки та аналізатор мови розмітки 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()
для створення 200-байтового рядка потрібно 88,7 мікросекунд (мільйони секунди), 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 секунд нова функція могла створити рядок, що містить 1166 890 359 символів. Для того щоб створити цей самий рядок, для старої функції знадобиться 7181884 секунди часу.
Це майже три місяці порівняно з десятьма секундами!
Я відповідаю лише (запізнившись на кілька років), оскільки моє оригінальне рішення цієї проблеми плаває по Інтернету вже більше 10 років, і, мабуть, мало хто розуміє цю проблему. Я думав, що, написавши тут статтю про це, я допоможу:
Оптимізація продуктивності для високошвидкісного JavaScript / Сторінка 3
На жаль, деякі інші рішення, представлені тут, все ще є деякими з тих, на які знадобиться три місяці, щоб отримати той самий обсяг випуску, який правильне рішення створює за 10 секунд.
Я хочу взяти час, щоб відтворити частину статті тут як канонічну відповідь на "Переповнення стека".
Зауважте, що алгоритм найкращої ефективності тут явно базується на моєму алгоритмі і, ймовірно, був успадкований від чужої адаптації третього чи четвертого поколінь. На жаль, модифікації призвели до зниження його продуктивності. Варіант мого рішення, представлений тут, можливо, не зрозумів моє заплутане for (;;)
вираження, схоже на основний нескінченний цикл сервера, написаний на C, і який був просто розроблений, щоб дозволити ретельно розміщений оператор розриву для управління циклом, найбільш компактний спосіб уникайте експоненціальної реплікації рядка один зайвий непотрібний час.
Цей досить ефективний
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
в даний час є частиною JavaScript .
"yo".repeat(2);
// returns: "yoyo"
Метод підтримується всіма основними браузерами, крім Internet Explorer та Android Webview. Щоб переглянути оновлений список, див. MDN: String.prototype.repeat> Сумісність браузера .
У MDN є поліфіл для браузерів без підтримки.
String.prototype.repeat тепер стандарт ES6.
'abc'.repeat(3); //abcabcabc
Розширення рішення П.Бейлі :
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
РЕДАКТУЙТЕ : Кредити для героя за його елегантну ++num
ідею!
String.prototype.repeat = function(n){return new Array(isNaN(n) ? 1 : ++n).join(this);}
/**
@desc: repeat string
@param: n - times
@param: d - delimiter
*/
String.prototype.repeat = function (n, d) {
return --n ? this + (d || '') + this.repeat(n, d) : '' + this
};
ось як повторити рядок кілька разів, використовуючи деліметр.
Ось на 5-7% покращення щодо відповіді недоброзичливця.
Відкрутіть цикл, зупинившись count > 1
і виконайте додатковий result += pattnern
конмат після циклу. Це дозволить уникнути попереднього невикористання кінцевих циклів pattern += pattern
без використання дорогої if-check. Кінцевий результат виглядатиме так:
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 . приєднатися.
Тести різних методів:
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(''); }
Якщо ви хочете порівняти ефективність обох варіантів, дивіться цю скрипку та цю скрипку для еталонів тестів. Під час моїх власних тестів другий варіант був приблизно в 2 рази швидший у Firefox і приблизно в 4 рази швидший у Chrome!
У сучасних браузерах тепер ви також можете це зробити:
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) разів швидше (залежно від браузера).
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
Fiddle: 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 страждає від однієї з його найбільших сильних сторін; він дуже терпимий до поганої поведінки, і настільки гнучкий, він буде нахилятися назад, щоб повернути результати, коли це, можливо, було б краще для всіх, якби воно зірвало!
"З великою силою приходить велика відповідальність" ;-)
Але більш серйозно і важливо, хоча загальні питання на кшталт цього призводять до приголомшливості у вигляді розумних відповідей, що, якщо нічого іншого, розширити свої знання та горизонти, врешті-решт, завдання, що знаходиться під рукою - практичний сценарій, що використовує отриманий метод - може знадобитися трохи менше, або трохи розумніші ніж пропонується.
Ці "ідеальні" алгоритми - це весело і все, але "один розмір підходить усім" рідко, якщо коли-небудь буде кращим, ніж зроблений на замовлення.
Ця проповідь була принесена вам люб’язно через недосипання та прохожі інтереси. Ідіть і кодуйте!
По-перше, питання ОП, здається, стосуються стислість - що, наскільки я розумію, означає "простий і легкий для читання", тоді як більшість відповідей, мабуть, стосується ефективності - що, очевидно, не те саме, а також я думаю, що якщо ви не реалізуєте дуже конкретні алгоритми маніпулювання великими даними, не повинні вас турбувати, коли ви прийдете реалізовувати основні функції Javascript для обробки даних. Лаконічність набагато важливіша.
По-друге, як зазначив Андре Ласло, String.repeat є частиною ECMAScript 6 і вже доступний у декількох популярних реалізаціях - тому найкоротша реалізація String.repeat
не полягає в її реалізації ;-)
Нарешті, якщо вам потрібно підтримати хости, які не пропонують реалізацію ECMAScript 6, поліфін MDN, згаданий Андре Ласло, є не що іншим, як стислим.
Тож без зайвих прихисток - ось мій стислий поліфайл:
String.prototype.repeat = String.prototype.repeat || function(n){
return n<=1 ? this : this.concat(this.repeat(n-1));
}
Так, це рекурсія. Мені подобаються рекурсії - вони прості, і якщо виконано правильно, їх легко зрозуміти. Щодо ефективності, якщо мова підтримує її, вони можуть бути дуже ефективними, якщо правильно написані.
З моїх тестів, цей метод на 60% швидший, ніж Array.join
підхід. Хоча це, очевидно, ніде не наближається до реалізації спокою, але це набагато простіше, ніж обидва.
Моя тестова установка - вузол v0.10, використовуючи "Суворий режим" (я думаю, що це дає змогу отримати якийсь TCO ), викликаючи repeat(1000)
рядок 10 символів мільйон разів.
Якщо ви вважаєте, що всі ці визначення прототипу, створення масивів та операції приєднання є надмірними, просто використовуйте єдиний код рядка там, де вам це потрібно. Рядок S, що повторюється N разів:
for (var i = 0, result = ''; i < N; i++) result += S;
Array(N + 1).join(str)
метод, якщо це не вузьке місце). Якщо є найменший шанс, що ви збираєтесь його використовувати двічі, перемістіть його на відповідну функцію.
Використовуйте функцію утиліти Lodash для утиліти Javascript, наприклад повторення рядків.
Lodash забезпечує приємну продуктивність та сумісність із ECMAScript.
Я дуже рекомендую його для розробки інтерфейсу, і він також добре працює на сервері.
Ось як повторити рядок "yo" 2 рази, використовуючи Lodash:
> _.repeat('yo', 2)
"yoyo"
Я прийшов сюди випадковим чином і ніколи раніше не мав причин повторювати знак у JavaScript.
Мене вразив спосіб це зробити artistoex та збитки від результатів. Я помітив, що останній струнний конмат був непотрібним, як і вказував Денніс.
Я помітив ще декілька речей, коли грав із вибірковим розбором, складеним разом.
Результати варіювали неабияку кількість, що часто сприяло останньому пробігу, і подібні алгоритми часто жокеювали позицію. Одним із речей, які я змінив, було замість використання рахунку, створеного 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% в середньому за виправлення Денніса. Однак різні запуски та різні браузери показали б досить справедливу дисперсію, що цей додатковий код, ймовірно, не вартий зусиль над двома попередніми алгоритмами.
Редагувати: Я робив це переважно під хромом. Firefox та IE часто віддають перевагу Деннісу на пару%.
Простий метод:
String.prototype.repeat = function(num) {
num = parseInt(num);
if (num < 0) return '';
return new Array(num + 1).join(this);
}
Люди надмірно ускладнюють це до смішної міри або марно працюють. Масиви? Рекурсія? Ти мусиш жартувати мене.
function repeat (string, times) {
var result = ''
while (times-- > 0) result += string
return result
}
Редагувати. Я провів кілька простих тестів, щоб порівняти з побітною версією, розміщеною artistoex / disfated, і купою інших людей. Останнє було лише незначно швидше, але на порядок ефективніше пам'яті. За 1000000 повторів слова "blah" процес Node збільшився до 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
половина часу.
З ES8 ви також можете використовувати padStart
або padEnd
для цього. напр.
var str = 'cat';
var num = 23;
var size = str.length * num;
"".padStart(size, str) // outputs: 'catcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcat'