Каков наилучший или самый сжатый способ для возврата строки, повторяемой произвольным количеством раз?
Следующий мой лучший снимок:
function repeat(s, n){
var a = [];
while(a.length < n){
a.push(s);
}
return a.join('');
}
Каков наилучший или самый сжатый способ для возврата строки, повторяемой произвольным количеством раз?
Следующий мой лучший снимок:
function repeat(s, n){
var a = [];
while(a.length < n){
a.push(s);
}
return a.join('');
}
Примечание для новых читателей: Этот ответ старый и не очень практичный - он просто "умный", потому что он использует материал Array для получения Струнные вещи сделаны. Когда я писал "меньше процесса", я определенно имел в виду "меньше кода", потому что, как другие отметили в последующих ответах, это как свинья. Поэтому не используйте его, если вам нужна скорость.
Я бы поставил эту функцию непосредственно на объект String. Вместо того, чтобы создавать массив, заполнять его и присоединять к нему с помощью пустого char, просто создайте массив соответствующей длины и присоедините его к нужной строке. Тот же результат, меньше процесса!
String.prototype.repeat = function( num )
{
return new Array( num + 1 ).join( this );
}
alert( "string to repeat\n".repeat( 4 ) );
Я проверил эффективность всех предложенных подходов.
Вот самый быстрый вариант, который у меня есть.
String.prototype.repeat = function(count) {
if (count < 1) return '';
var result = '', pattern = this.valueOf();
while (count > 1) {
if (count & 1) result += pattern;
count >>= 1, pattern += pattern;
}
return result + pattern;
};
Или как автономная функция:
function repeat(pattern, count) {
if (count < 1) return '';
var result = '';
while (count > 1) {
if (count & 1) result += pattern;
count >>= 1, pattern += pattern;
}
return result + pattern;
}
Он основан на алгоритме artistoex.
Это действительно быстро. И чем больше count
, тем быстрее он идет по сравнению с традиционным подходом new Array(count + 1).join(string)
.
Я только изменил 2 вещи:
pattern = this
на pattern = this.valueOf()
(очищает одно очевидное преобразование типов);if (count < 1)
из prototypejs вверху функции, чтобы исключить ненужные действия в этом случае.UPD
Создана небольшая площадка для тестирования производительности здесь для тех, кто заинтересован.
переменная count
~ 0.. 100:
постоянная count
= 1024:
Используйте его и сделайте еще быстрее, если сможете :)
Эта проблема является известной/ "классической" проблемой оптимизации для JavaScript, вызванной тем фактом, что строки JavaScript "неизменяемы" и добавление путем конкатенации даже одного символа в строку требует создания, включая выделение памяти для и копирование в целую новую строку.
К сожалению, принятый ответ на этой странице неверен, где "неправильный" означает коэффициент производительности 3x для простых односимвольных строк и 8x-97x для коротких строк, повторяющихся больше раз, до 300x для повторения предложений и бесконечно ошибочно при переходе к пределу соотношений сложности алгоритмов как n
переходит в бесконечность. Кроме того, есть еще один ответ на этой странице, который почти прав (основанный на одном из многих поколений и вариантах правильного решения, распространяющегося по всему Интернету за последние 13 лет). Однако это "почти правильное" решение пропускает ключевую точку правильного алгоритма, что приводит к снижению производительности на 50%.
~ October 2000 Я опубликовал алгоритм этой точной проблемы, который был широко адаптирован, модифицирован, а затем в конечном итоге плохо понят и забыт. Чтобы исправить эту проблему, в августе 2008 года я опубликовал статью http://www.webreference.com/programming/javascript/jkm3/3.html, объясняющую алгоритм и использующий ее как пример простого общего назначения Оптимизация 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;
}
В течение двух месяцев после публикации этой статьи этот же вопрос был отправлен в Qaru и летел под моим радаром до сих пор, когда, по-видимому, первоначальный алгоритм этой проблемы еще раз был забыт. Лучшим решением, доступным на этой странице, является модифицированная версия моего решения, возможно, разделенная на несколько поколений. К сожалению, модификации разрушили оптимальность решения. Фактически, изменяя структуру цикла из моего оригинала, модифицированное решение выполняет полностью ненужный дополнительный шаг экспоненциального дублирования (таким образом, соединяя самую большую строку, используемую в правильном ответе с собой дополнительное время, а затем отбрасывая ее).
Ниже приводится обсуждение некоторых оптимизаций JavaScript, связанных со всеми ответами на эту проблему и на благо всех.
Чтобы проиллюстрировать, как работает этот метод, мы используем реальную функцию JavaScript, которая создает строки любой длины. И, как мы увидим, можно добавить больше оптимизаций!
Функция, подобная используемой здесь, заключается в создании прокладки для выравнивания столбцов текста, форматирования денег или заполнения данных блока до границы. Функция генерации текста также позволяет вводить переменную длину для тестирования любой другой функции, которая работает с текстом. Эта функция является одним из важных компонентов модуля обработки текста JavaScript.
По мере продолжения мы рассмотрим еще два наиболее важных метода оптимизации при разработке исходного кода в оптимизированный алгоритм создания строк. Конечным результатом является промышленно-прочная и высокопроизводительная функция, которую я использовал повсюду - выравнивание цен и итогов товаров в форматах заказов на JavaScript, форматирование данных и форматирование электронной почты и текстовых сообщений и многое другое.
Исходный код для создания строк stringFill1()
function stringFill1(x, n) {
var s = '';
while (s.length < n) s += x;
return s;
}
/* Example of output: stringFill1('x', 3) == 'xxx' */
Синтаксис здесь ясен. Как вы можете видеть, мы уже использовали локальные функциональные переменные, прежде чем переходить к большей оптимизации.
Имейте в виду, что в коде есть одна невинная ссылка на свойство объекта s.length
, которая ущемляет его производительность. Хуже того, использование этого свойства объекта уменьшает простоту программы, если предположить, что читатель знает о свойствах строковых объектов JavaScript.
Использование этого свойства объекта разрушает общность компьютерной программы. Программа предполагает, что x
должна быть строкой длиной один. Это ограничивает применение функции stringFill1()
чем угодно, кроме повторения отдельных символов. Даже одиночные символы нельзя использовать, если они содержат несколько байтов, таких как объект HTML
.
Худшая проблема, вызванная этим ненужным использованием свойства объекта, заключается в том, что функция создает бесконечный цикл, если тестируется на пустой строке ввода x
. Чтобы проверить общность, примените программу к минимально возможному количеству ввода. У программы, которая возникает при запросе на превышение объема доступной памяти, есть оправдание. Программа, подобная этой, которая терпит неудачу, когда ее попросят произвести ничего, неприемлема. Иногда красивый код - ядовитый код.
Простота может быть двусмысленной целью компьютерного программирования, но в целом это не так. Когда в программе не хватает разумного уровня общности, нельзя сказать, что "Программа достаточно хороша, насколько это возможно". Как вы можете видеть, использование свойства string.length
не позволяет этой программе работать в общей настройке, и на самом деле некорректная программа готова вызвать сбои браузера или системы.
Есть ли способ улучшить производительность JavaScript, а также решить эти две серьезные проблемы?
Конечно. Просто используйте целые числа.
Оптимизированный код для создания строк stringFill2()
function stringFill2(x, n) {
var s = '';
while (n-- > 0) s += x;
return s;
}
Временной код для сравнения stringFill1()
и stringFill2()
function testFill(functionToBeTested, outputSize) {
var i = 0, t0 = new Date();
do {
functionToBeTested('x', outputSize);
t = new Date() - t0;
i++;
} while (t < 2000);
return t/i/1000;
}
seconds1 = testFill(stringFill1, 100);
seconds2 = testFill(stringFill2, 100);
Успех до сих пор stringFill2()
stringFill1()
занимает 47.297 микросекунд (миллионные доли секунды), чтобы заполнить 100-байтовую строку, а stringFill2()
занимает 27.68 микросекунд, чтобы сделать то же самое. Это почти удвоение производительности, избегая ссылки на свойство объекта.
Наш предыдущий результат выглядел хорошо - очень хорошо, на самом деле. Улучшенная функция stringFill2()
намного быстрее благодаря использованию наших первых двух оптимизаций. Вы поверите, если бы я сказал вам, что его можно улучшить во много раз быстрее, чем сейчас?
Да, мы можем достичь этой цели. Прямо сейчас нам нужно объяснить, как избежать добавления коротких строк в длинные строки.
Кратковременное поведение кажется довольно хорошим, по сравнению с нашей первоначальной функцией. Ученым-компьютерщикам нравится анализировать "асимптотическое поведение" алгоритма функции или компьютерной программы, что означает изучение его долговременного поведения путем тестирования его с большими входами. Иногда, не выполняя дальнейших тестов, никогда не осознается, как можно улучшить компьютерную программу. Чтобы узнать, что произойдет, мы создадим 200-байтную строку.
Проблема с stringFill2()
Используя нашу функцию синхронизации, мы обнаруживаем, что время увеличивается до 62,54 микросекунд для 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 of N, и это означает, что вычислительная стоимость в конечном счете пропорциональна квадрату длины строки. Для создания 100 символов требуется 10 000 единиц работы, а для создания 200 символов требуется 40 000 единиц работы.
Вот почему потребовалось более двух раз, чтобы создать 200 символов, чем 100 символов. Фактически, это должно было пройти в четыре раза. Наш опыт программирования был правильным в том, что работа выполняется немного более эффективно для более длинных строк, и, следовательно, это заняло всего около трех раз. Когда накладные расходы на вызов функции становятся незначительными в отношении того, как долго мы создаем строку, на самом деле потребуется в четыре раза больше времени для создания строки в два раза.
(Историческая заметка: этот анализ не обязательно относится к строкам в исходном коде, например html = 'abcd\n' + 'efgh\n' + ... + 'xyz.\n'
, так как компилятор исходного кода JavaScript может присоединиться к строкам вместе, прежде чем превращать их в объект строкой JavaScript. Всего несколько лет назад, реализация KJS JavaScript замерзла или сработала при загрузке длинных строк исходного кода, соединенных знаками плюса. Поскольку время вычислений было O(N^2)
, было непросто сделать веб-страницы, которые перегружали веб-браузер Konqueror или Safari, что использовал ядро JavaScript KJS JavaScript. Сначала я столкнулся с этой проблемой, когда я разрабатывал язык разметки и парсер разметки JavaScript, а затем я обнаружил, что вызывает проблему, когда я написал свой script для включения 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
оценивает значение true, если последняя двоичная цифра n
равна 1, а false, если последняя двоичная цифра n
равна 0.
Новая высокоэффективная функция stringFill3()
function stringFill3(x, n) {
var s = '';
for (;;) {
if (n & 1) s += x;
n >>= 1;
if (n) x += x;
else break;
}
return s;
}
Он выглядит уродливым для неподготовленного глаза, но его производительность - не что иное, как прекрасное.
Посмотрите, насколько хорошо эта функция работает. После просмотра результатов, вероятно, вы никогда не забудете разницу между алгоритмом O(N^2)
и алгоритмом O(N)
.
stringFill1()
берет 88,7 микросекунды (миллионные доли секунды) для создания 200-байтовой строки, stringFill2()
занимает 62,54, а stringFill3()
занимает всего 4.608. Что сделал этот алгоритм намного лучше? Все функции воспользовались использованием локальных функциональных переменных, но с использованием второй и третьей методик оптимизации было добавлено двадцатикратное улучшение производительности stringFill3()
.
Более глубокий анализ
Что заставляет эту конкретную функцию вытеснять конкуренцию из воды?
Как я уже упоминал, причина, по которой обе эти функции, stringFill1()
и stringFill2()
, работают так медленно, что строки JavaScript неизменяемы. Память не может быть перераспределена, чтобы добавить еще один байт к строковым данным, хранящимся в JavaScript. Каждый раз, когда в конец строки добавляется еще один байт, вся строка восстанавливается от начала до конца.
Таким образом, чтобы улучшить производительность script, нужно предварительно скопировать строки длинной длины, объединив две строки вместе раньше времени, а затем рекурсивное построение требуемой длины строки.
Например, чтобы создать строку с 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, поэтому предыдущий алгоритм занимает намного больше времени для создания строки.
Чтобы сравнить два метода, вы можете увидеть, насколько быстрее рекурсивный алгоритм (также называемый "divide and conquer" ) находится на строке длиной 123,457. На моем компьютере FreeBSD этот алгоритм, реализованный в функции stringFill3()
, создает строку в 0.001058 секунд, а оригинальная функция stringFill1()
создает строку в 0.0808 секунд. Новая функция в 76 раз быстрее.
Разница в производительности возрастает по мере увеличения длины строки. В пределе по мере создания больших и больших строк исходная функция ведет себя примерно как C1
(константа) раз N^2
, а новая функция ведет себя как C2
(константа) раз n
.
Из нашего эксперимента мы можем определить значение C1
как C1 = 0.0808 / (123457)2 = .00000000000530126997
, а значение C2
должно быть C2 = 0.001058 / 123457 = .00000000856978543136
. Через 10 секунд новая функция может создать строку, содержащую 1 166 890 359 символов. Чтобы создать эту же строку, старой функции потребуется 7 218 384 секунды времени.
Это почти три месяца по сравнению с десятью секундами!
Я только отвечаю (несколько лет спустя), потому что мое первоначальное решение этой проблемы уже более 10 лет плавает по Интернету и, по-видимому, мало кто понимает немногие, кто это помнит. Я думал, что, написав статью об этом здесь, я бы помог:
Оптимизация производительности для высокоскоростного JavaScript/Страница 3
К сожалению, некоторые из других решений, представленных здесь, по-прежнему являются одними из тех, которые занимают три месяца, чтобы получить тот же объем вывода, который правильное решение создает за 10 секунд.
Я хочу потратить время на воспроизведение части статьи здесь как канонический ответ на переполнение стека.
Обратите внимание, что наиболее эффективный алгоритм здесь явно основан на моем алгоритме и, вероятно, унаследован от адаптации третьего или четвертого поколения. К сожалению, изменения привели к снижению его производительности. Вариант моего решения, представленный здесь, возможно, не понимал моего запутанного выражения for (;;)
, которое выглядит как основной бесконечный цикл сервера, написанного на C, и который был просто разработан, чтобы позволить тщательно позиционированный оператор break для управления контуром, наиболее компактный способ избежать экспоненциальной репликации строки лишнего лишнего времени.
Этот довольно эффективный
String.prototype.repeat = function(times){
var result="";
var pattern=this;
while (times > 0) {
if (times&1)
result+=pattern;
times>>=1;
pattern+=pattern;
}
return result;
};
Хорошие новости! String.prototype.repeat
is принято для Гармонии (ECMAScript 6).
> "yo".repeat(2)
"yoyo"
Этот метод доступен в последних версиях V8, используемых Node.js, Chrome (String.repeat
поддерживается начиная с версии 41) и Opera. Более новые версии Safari и Firefox, похоже, также поддерживают, но Internet Explorer - нет. Список обновлений см. в разделе MDN: String.prototype.repeat> Совместимость с браузерами.
MDN предлагает следующее заполнение:
if (!String.prototype.repeat) {
String.prototype.repeat = function(count) {
'use strict';
if (this == null)
throw new TypeError('can\'t convert ' + this + ' to object');
var str = '' + this;
// To convert string to integer.
count = +count;
// Check NaN
if (count != count)
count = 0;
if (count < 0)
throw new RangeError('repeat count must be non-negative');
if (count == Infinity)
throw new RangeError('repeat count must be less than infinity');
count = Math.floor(count);
if (str.length == 0 || count == 0)
return '';
// Ensuring count is a 31-bit integer allows us to heavily optimize the
// main part. But anyway, most current (August 2014) browsers can't handle
// strings 1 << 28 chars or longer, so:
if (str.length * count >= 1 << 28)
throw new RangeError('repeat count must not overflow maximum string size');
var maxCount = str.length * count;
count = Math.floor(Math.log(count) / Math.log(2));
while (count) {
str += str;
count--;
}
str += str.substring(0, maxCount - str.length);
return str;
}
}
String.prototype.repeat теперь является стандартом ES6.
'abc'.repeat(3); //abcabcabc
Расширение Решение P.Bailey:
String.prototype.repeat = function(num) {
return new Array(isNaN(num)? 1 : ++num).join(this);
}
Таким образом, вы должны быть в безопасности от неожиданных типов аргументов:
var foo = 'bar';
alert(foo.repeat(3)); // Will work, "barbarbar"
alert(foo.repeat('3')); // Same as above
alert(foo.repeat(true)); // Same as foo.repeat(1)
alert(foo.repeat(0)); // This and all the following return an empty
alert(foo.repeat(false)); // string while not causing an exception
alert(foo.repeat(null));
alert(foo.repeat(undefined));
alert(foo.repeat({})); // Object
alert(foo.repeat(function () {})); // Function
EDIT: Кредиты jerone за его элегантную идею ++num
!
Используйте 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
};
вот как несколько раз повторять строку с использованием разделителя.
Здесь улучшается 5-7% от разрешенного ответа.
Разверните цикл, остановив его на count > 1
и выполните следующий result += pattnern
concat после цикла. Это позволит избежать окончательных циклов, ранее не использованных pattern += pattern
, не используя дорогостоящую проверку if.
Окончательный результат будет выглядеть следующим образом:
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 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. Говоря грубо говоря, 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/String/repeat
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
Еще одна функция повторения:
function repeat(s, n) {
var str = '';
for (var i = 0; i < n; i++) {
str += s;
}
return str;
}
Это может быть наименьший рекурсивный: -
String.prototype.repeat = function(n,s) {
s = s || ""
if(n>0) {
s += this
s = this.repeat(--n,s)
}
return s}
Если вы считаете, что все эти определения прототипов, создания массивов и операции соединения являются чрезмерными, просто используйте один строковый код, где он вам нужен. Строка S повторяется N раз:
for (var i = 0, result = ''; i < N; i++) result += S;
Fiddle: http://jsfiddle.net/3Y9v2/
function repeat(s, n){
return ((new Array(n+1)).join(s));
}
alert(repeat('R', 10));
Я просто хотел дать ему bash и сделал это:
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 страдает одной из самых сильных сторон; он очень терпим к плохому поведению, и настолько гибкий, что он будет склоняться назад, чтобы возвращать результаты, когда это могло бы быть лучше для всех, если бы оно защелкнулось!
"С великой силой приходит большая ответственность"; -)
Но более серьезно и важно, хотя общие вопросы, подобные этому, приводят к удивительности в виде умных ответов, которые, если не что иное, расширяют одно знание и горизонты, в конце концов, задача под рукой - практический script, который использует результирующий метод - может потребоваться немного меньше или немного более умный, чем предлагается.
Эти "идеальные" алгоритмы - это весело и все, но "один размер подходит всем" редко будет когда-либо лучше, чем сделанный на заказ.
Эта проповедь была принесена вам любезностью из-за недостатка сна и мимолетного интереса. Иди вперед и код!
Во-первых, вопросы OP, похоже, касаются краткости, которые, как я понимаю, означают "простые и легко читаемые", в то время как большинство ответов, похоже, касаются эффективности, что, очевидно, не одно и тоже, и я думаю, что если вы реализовать некоторые очень специфичные алгоритмы управления большими данными, не стоит беспокоиться о вас, когда вы приступаете к реализации основных функций Javascript для обработки данных. Совместимость гораздо важнее.
Во-вторых, как отметил Андре Ласло, String.repeat является частью ECMAScript 6 и уже доступен в нескольких популярных реализациях - поэтому наиболее краткая реализация String.repeat
заключается не в его реализации; -)
Наконец, если вам нужно поддерживать хосты, которые не предлагают реализацию ECMAScript 6, MDN polyfill, упомянутый Андре Ласло, не что иное, как краткий.
Итак, без дальнейших церемоний - вот мой лаконичный polyfill:
String.prototype.repeat = String.prototype.repeat || function(n){
return n<=1 ? this : this.concat(this.repeat(n-1));
}
Да, это рекурсия. Мне нравятся рекурсии - они просты и, если все сделано правильно, легко понять. Что касается эффективности, если язык поддерживает это, они могут быть очень эффективными, если они написаны правильно.
Из моих тестов этот метод ~ на 60% быстрее, чем подход Array.join
. Несмотря на то, что он явно не закрывается, он намного проще, чем оба.
Моя тестовая установка node v0.10, используя "Строгий режим" (я думаю, что она позволяет использовать TCO), вызывая repeat(1000)
в строке из 10 символов в миллион раз.
Используйте утилиту Lodash для Javascript, например, повторяющиеся строки.
Lodash обеспечивает хорошую производительность и совместимость с ECMAScript.
Я очень рекомендую его для разработки пользовательского интерфейса, и он хорошо работает и на стороне сервера.
Здесь, как повторить строку "yo" 2 раза, используя Lodash:
> _.repeat('yo', 2)
"yoyo"
Рекурсивное решение с использованием divide и conquer:
function repeat(n, s) {
if (n==0) return '';
if (n==1 || isNaN(n)) return s;
with(Math) { return repeat(floor(n/2), s)+repeat(ceil(n/2), s); }
}
Я пришел сюда случайно и никогда не имел причины повторять char в javascript раньше.
Меня впечатлил способ artoex сделать это и удалить результаты. Я заметил, что последняя строка concat не нужна, как заметил Деннис.
Я заметил еще несколько вещей, когда играл с дискретизацией, собранной вместе.
Результаты варьировались от справедливой суммы, часто в пользу последнего прогона, и подобные алгоритмы часто жокей за позицию. Одна из вещей, которые я изменила, - вместо использования генерируемого JSLitmus count в качестве семени для вызовов; поскольку счетчик был сгенерирован другим для разных методов, я ввел индекс. Это сделало вещь более надежной. Затем я посмотрел на то, что переменные размера передавались в функции. Это предотвратило некоторые из изменений, которые я видел, когда некоторые алгоритмы улучшались в одиночных символах или меньших строках. Тем не менее, лучшие 3-х методы все сделали хорошо, независимо от размера строки.
Установленный тестовый набор
http://jsfiddle.net/schmide/fCqp3/134/
// repeated string
var string = '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789';
// count paremeter is changed on every test iteration, limit it 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 повторений слова "бла" процесс Node достиг до 46 мегабайт с помощью простого алгоритма конкатенации (см. Выше), но только с 5,5 мегабайтами с логарифмическим алгоритмом. Последнее, безусловно, путь. Отправляя его для ясности:
function repeat (string, times) {
var result = ''
while (times > 0) {
if (times & 1) result += string
times >>= 1
string += string
}
return result
}
Это то, что вы ищете, я надеюсь. Посмотрите исходную реализацию, которую я нашел в исходном коде node.js.
function repeatString(str, len) {
return Array.apply(null, {
length: len + 1
}).join(str).slice(0, len)
}
Конкатенация строк на основе числа.
function concatStr(str, num) {
var arr = [];
//Construct an array
for (var i = 0; i < num; i++)
arr[i] = str;
//Join all elements
str = arr.join('');
return str;
}
console.log(concatStr("abc", 3));
Надеюсь, что это поможет!
С ES8 вы также можете использовать padStart
или padEnd
для этого. например.
var str = 'cat';
var num = 23;
var size = str.length * num;
"".padStart(size, str) // outputs: 'catcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcat'