Конкатенация умножением C#
Можно ли как-то совершить конкатенацию одной строки умножением?
Допустим есть строка string str = "-"; и ее нужно умножить на 3, чтобы получить ---
Надумал такую конструкцию
string str = "=";
for(int i = 0; i != 5; i++) str += "=";
Console.Write(str);
Eсли есть Есть альтернатива, напишите пожалуйста
Ответы (3 шт):
Можно, для символа это делается так, через конструктор строки:
string str = new string('-', 5);
Если нужно повторять не символ, а строку, можно для компактности использовать Linq:
string str = string.Concat(Enumerable.Repeat("hello", 3));
Упд1: Добавлена реализация
Упд2: Добавлен бенчмарк
Для строки повторяющейся из символа - решения есть, это конструктор строки, он позволяет создать строку из одного и того же символа "умножением" (ваша терминология). И похоже что вменяемых решений (из коробки), так что бы можно было создать строку повторением строки - нету. Проблема тут в том, что итоговая строка будет "повторяться" из предыдущих конкатенаций, и вы наплодите множество промежуточных строк в любом случае. А этого нужно избегать (зачем плодить объекты?).
Обрисуем проблему
str = "тест"; // Нужно повторить строку (например) 8 раз.
Конкатенация подразумевает следующее:
str = "тест"; // объект "тест"
str += "тест"; // объект "тесттест" (x2)
str += "тест"; // объект "тесттесттест" (x3)
str += "тест"; // объект "тесттесттесттест" (x4)
str += "тест"; // объект "тесттесттесттесттест" (x5)
str += "тест"; // объект "тесттесттесттесттесттест" (x6)
str += "тест"; // объект "тесттесттесттесттесттесттест" (x7)
str += "тест"; // объект "тесттесттесттесттесттесттесттест" (x8)
Самое очевидное решение на конкатенации - это повторное использование удвоенных строк, учетверённых строк, увосьмерённых строк, и так далее. Для этого нужно найти степень двойки вашего "множителя". Если множитель "8" - то степень двойки этого множителя - и есть "8" (2^3=8). Что это значит? Это значит повторяйте строку до этой самой степени двойки, и конкатенируйте с собой же:
str = "тест"; // объект "тест"
str += str; // объект "тесттест" (x2)
str += str; // объект "тесттесттесттест" (x4)
str += str; // объект "тесттесттесттесттесттесттесттест" (x8)
Но тут есть проблема, не все "множители" - есть степень двойки, и от этого останется "хвост". А хвост - это тоже некоторая степень двойки.
Возьмём для примера "множитель" 23.
Степень двойки от 23 - это 16; 23 - 16 = 7
Степень двойки от 7 - это 4; 7 - 4 = 3
Степень двойки от 3 - это 2; 3 - 2 = 1
Конец
Это значит, что 23 повторения сведутся к 16 повторениям (степень двойки). А 16 повторений, это сильно оптимизированные конкатенации, которых вовсе не 16 штук, а 4 штуки.
str = "тест"; // объект "тест"
str += str; // объект "тесттест" (x2)
str += str; // объект "тесттесттесттест" (x4)
str += str; // объект "тесттесттесттесттесттесттесттест" (x8)
str += str; // объект "тесттесттесттесттест...тесттесттесттест" (x16)
И ровно так же конкатенируйте "хвосты", потому что любые хвосты от "множителя" - это тоже степень двойки.
Т.е. в общем алгоритмическом смысле это всё можно свести не к N конкатенациям, где кол-во объектов будет грандиозным, а к LogN конкатенациям - что сильно лучше.
И на самом деле кол-во конкатенаций будет - двоичное представление "множителя" и LogN от каждого единичного бита.
И это всё нет смысла хранить в объектах, всё это нужно пихать в единственный stackalloc.
Упд1: Реализация на конкатенациях, и это не сложно. В начале как обычно кучи проверок на всякое непотребное, а вторая половина - собственно сам алгоритм, и он почти тривиален. И это по сути пять строк.
public static class extensions
{
public static string GetMultipleString(this string str, int multiplier)
{
if (multiplier <= 0)
throw new System.ArgumentOutOfRangeException("multiplier", "'multiplier' must be positive");
if (str == null) return null;
if (multiplier == 1) return str;
int strLen = str.Length;
if (strLen == 0) return str;
if (strLen == 1) return new string(str[0], multiplier);
string strOut = null; //int loopCounter = 0;
do
{
if ((multiplier & 1) == 1)
strOut += str;
str += str;
multiplier >>= 1;
//loopCounter++;
}
while (multiplier > 0);
//System.Console.WriteLine("loops {0}", loopCounter);
return strOut;
}
}
Использование
//System.Console.WriteLine("".GetMultipleString(0)); // Failed
//System.Console.WriteLine("abc".GetMultipleString(0));// Failed
System.Console.WriteLine("".GetMultipleString(1)); // <empty>
System.Console.WriteLine("".GetMultipleString(2)); // <empty>
System.Console.WriteLine("a".GetMultipleString(1)); // a
System.Console.WriteLine("a".GetMultipleString(2)); // aa
System.Console.WriteLine("qp".GetMultipleString(1)); // qp
System.Console.WriteLine("qp".GetMultipleString(2)); // qpqp
System.Console.WriteLine("xyz".GetMultipleString(1)); // xyz
System.Console.WriteLine("xyz".GetMultipleString(2)); // xyzxyz
А теперь фокус: Раскомментируйте закомментированное в цикле, и вы получите кол-во конкатенаций. Используйте значение multiplier например равное 1000, и вы получите loopCounter=10, это значит что происходит не более 20 конкатенаций, и это на 1000 повторений строки.
Гипертест
for (int multiplier = 1; multiplier <= 1000; multiplier++)
{
string str = "ab".GetMultipleString(multiplier);
System.Console.WriteLine("mult {0}, len {1}, {2}", multiplier, str.Length, str);
}
Упд2: Микробенчмарк
public volatile string volatileString;
public void Code0()
{
string str = null;
for (int i = 0; i < 1000000; i++)
{
// строка из 1000 повторений "ab"
// эта строка создаётся миллион раз
str = "ab".GetMultipleString(1000);
}
this.volatileString = str;
}
public volatile string volatileString;
public void Code1()
{
string str = null;
System.Text.StringBuilder
sb = new System.Text.StringBuilder(2000, 2000);
for (int i = 0; i < 1000000; i++)
{
sb.Clear();
for (int j = 0; j < 1000; j++)
{
// строка из 1000 повторений "ab"
// эта строка создаётся миллион раз
sb.Append("ab");
}
str = sb.ToString();
}
this.volatileString = str;
}
Методы обёрнуты обычным DateTime, используется не майкрософтовский Benchmark - он чистит рантайм и тогда запускается, и это читерские условия.
Результаты:
Миллион строк (каждая x1000 раз "ab")
Code0() использует string.GetMultipleString() ~3150ms
Code1() использует StringBuilder.Append() ~4600ms
Dot.NET Framework v4.7.2
Build Release x64
Station Win7Ent i5-3570k <Z77> 16Gb (no boosts, all locked)
Вставлю свои пять копеек)
PythonString str = "1";
str = str * 3;
public class PythonString
{
private string _value;
public PythonString(string value)
{
_value = value;
}
public static string operator *(PythonString a, int count)
{
if (count <= 0 || a?._value == null)
{
return string.Empty;
}
return string.Concat(Enumerable.Repeat(a._value, count));
}
public static implicit operator string(PythonString str)
{
return str?._value;
}
public static implicit operator PythonString(string str)
{
return new PythonString(str);
}
}