Как эффективно переписать части строки по индексу в .NET? - PullRequest
3 голосов
/ 11 августа 2010

В моей программе .NET я позволяю пользователю определять «поля», которые являются значениями, рассчитанными бизнес-логикой.Эти поля имеют позицию и длину, так что все они могут быть вставлены в одну выходную строку по заданному индексу.Я также разрешаю пользователю указывать содержимое этой выходной строки по умолчанию.Если поле для замены заданной позиции не определено, вместо него выводится символ по умолчанию

Мой вопрос: как я могу сделать это эффективно?Класс StringBuilder имеет метод Insert (int index, string value) , но это удлиняет выходную строку каждый раз, а не перезаписывает ее.Должен ли я устанавливать каждый символ по одному, используя индексатор StringBuilder [int index] , и это неэффективно?Поскольку я собираюсь делать это много раз, я бы хотел, чтобы это было как можно быстрее.

Спасибо.

Ответы [ 7 ]

3 голосов
/ 11 августа 2010

Делать это по одному символу за раз, вероятно, будет лучшим выбором.Я говорю это потому, что вызов Insert и Remove для StringBuilder приводит к смещению символов вправо / влево, точно так же, как аналогичные методы в любой изменяемой индексированной коллекции, такой как List<char>.

Тем не менее, это отличный кандидат на метод расширения, чтобы сделать вашу жизнь немного проще.

public static StringBuilder ReplaceSubstring(this StringBuilder stringBuilder, int index, string replacement)
{
    if (index + replacement.Length > stringBuilder.Length)
    {
        // You could throw an exception here, or you could just
        // append to the end of the StringBuilder -- up to you.
        throw new ArgumentOutOfRangeException();
    }

    for (int i = 0; i < replacement.Length; ++i)
    {
        stringBuilder[index + i] = replacement[i];
    }

    return stringBuilder;
}

Пример использования:

var builder = new StringBuilder("My name is Dan.");
builder.ReplaceSubstring(11, "Bob");

Console.WriteLine(builder.ToString());

Вывод:

My name is Bob.
2 голосов
/ 11 августа 2010

Класс StringBuilder позволяет создавать изменяемую строку.Попробуйте использовать функцию Remove перед выполнением Insert.Поскольку он доступен случайным образом, он должен быть очень быстрым.Пока StringBuilder сохраняет ту же емкость, копирование строк в памяти не займет много времени.Если вы знаете, что строка станет длиннее, попробуйте установить емкость на большую, когда вы звоните New StringBuilder()

1 голос
/ 11 августа 2010

Пока строки являются неизменяемыми, каждая манипуляция с ними будет вызывать загрузку GC, даже вызовы вставки / удаления StringBuilder. Я бы вырезал исходную строку по точкам вставки, а затем «заархивировал» ее данными, которые нужно вставить. После этого вы можете просто объединить строки внутри списка, чтобы получить результирующую строку.

Вот пример кода, который выполняет операции split / zip. Предполагается, что поля определены как число (позиция, длина, значение).

public class Field
{
    public int pos { get; set; }
    public int len { get; set; }
    public string value { get; set; }
    public string tag { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var source = "You'r order price [price] and qty [qty].";
        var fields = new List<Field>();
        fields.Add(new Field()
        {
            pos = 18, 
            len = 7, 
            value = "15.99$",
            tag = "price"
        });
        fields.Add(new Field()
        {
            pos = 37-3,
            len = 5,
            value = "7",
            tag = "qty"
        });
        Console.WriteLine(Zip(Split(source, fields), fields));
        Console.WriteLine(ReplaceRegex(source, fields));

    }

    static IEnumerable<string> Split(string source, IEnumerable<Field> fields)
    {
        var index = 0;
        foreach (var field in fields.OrderBy(q => q.pos))
        {
            yield return source.Substring(index, field.pos - index);
            index = field.pos + field.len;
        }
        yield return source.Substring(index, source.Length - index);
    }
    static string Zip(IEnumerable<string> splitted, IEnumerable<Field> fields)
    {
        var items = splitted.Zip(fields, (l, r) => new string[] { l, r.value }).SelectMany(q => q).ToList();
        items.Add(splitted.Last());
        return string.Concat(items);
    }
    static string ReplaceRegex(string source, IEnumerable<Field> fields)
    {
        var fieldsDict = fields.ToDictionary(q => q.tag);
        var re = new Regex(@"\[(\w+)\]");
        return re.Replace(source, new MatchEvaluator((m) => fieldsDict[m.Groups[1].Value].value));
    }
}

Кстати, было бы лучше заменить специальные пользовательские маркеры, такие как [цена], [кол-во], используя регулярное выражение?

0 голосов
/ 26 мая 2016

Как вы справедливо заявили, StringBuilder имеет метод Insert, но не метод Overwrite.
Итак, я создал метод расширения Overwrite, см. Ниже, для моих проектов.
Обратите внимание, что оно будет сокращать значение, если для StringBuilder недостаточно места для него. Однако вы можете легко изменить его логику.

    public static void Overwrite( this StringBuilder sb, int index, string value )
    {
        int len = Math.Min( value.Length, sb.Length - index );
        sb.Remove( index, len );
        sb.Insert( index, value.Substring( 0, len ) );
    }
0 голосов
/ 11 августа 2010

Если замена подстрок будет большим узким местом, вы можете вообще отказаться от подстрок.Вместо этого разбейте ваши данные на строки, которые могут быть независимо изменены.Примерно так:

class DataLine
{
    public string Field1;
    public string Field2;
    public string Field3;

    public string OutputDataLine()
    {
        return Field1 + Field2 + Field3;
    }
}

Это простой статический пример, но я уверен, что его можно было бы сделать более общим, чтобы каждый пользователь мог по-разному определять поля, чтобы вы могли обрабатывать его.После разбиения ваших данных на поля, если вам все еще нужно изменить отдельные символы в полях, по крайней мере, вы не касаетесь всего набора данных.

Теперь это может подтолкнуть шею к функции OutputDataLineв зависимости от того, что вы делаете с данными.Но это может быть обработано отдельно при необходимости.

0 голосов
/ 11 августа 2010

Если ваша строка уже предварительно отформатирована для длины, класс StringBuilder имеет

public StringBuilder Replace(string oldValue, string newValue, int startIndex, int count)

, просто установите начальный индекс и количество = 1, чтобы вы могли заменить этот конкретный экземпляр.

Другая вещь, которую вы можете сделать, это использовать String.Format ().Преобразуйте все предварительно определенные поля в индексы, чтобы получить строку типа «This {0} is very {1}», а затем просто сопоставить параметры с конкретным индексом и выполнить String.Format (myString, myParams);

-Raul

0 голосов
/ 11 августа 2010

Я бы рекомендовал использовать класс StringBuilder. Однако вы можете сделать это со строкой, но могут быть побочные эффекты. Вот пара сообщений в блоге, которые показывают, как управлять строками и возможными побочными эффектами.

http://philosopherdeveloper.wordpress.com/2010/05/28/are-strings-really-immutable-in-net/

http://philosopherdeveloper.wordpress.com/2010/06/13/string-manipulation-in-net-epilogue-plus-new-theme/

...