Конкатенация умножением C#

Можно ли как-то совершить конкатенацию одной строки умножением?
Допустим есть строка string str = "-"; и ее нужно умножить на 3, чтобы получить ---
Надумал такую конструкцию

string str = "=";
for(int i = 0; i != 5; i++) str += "=";
Console.Write(str);

Eсли есть Есть альтернатива, напишите пожалуйста


Ответы (3 шт):

Автор решения: aepot

Можно, для символа это делается так, через конструктор строки:

string str = new string('-', 5);

Если нужно повторять не символ, а строку, можно для компактности использовать Linq:

string str = string.Concat(Enumerable.Repeat("hello", 3));
→ Ссылка
Автор решения: InterceptorTSK

Упд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)
→ Ссылка
Автор решения: Aarnihauta

Вставлю свои пять копеек)

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);
    }
}
→ Ссылка