如何在.NET中通过索引有效地覆盖字符串的一部分?

发布于 2024-09-13 17:28:34 字数 350 浏览 7 评论 0原文

在我的 .NET 程序中,我允许用户定义“字段”,它们是由业务逻辑计算的值。这些字段具有位置和长度,因此它们都可以插入到给定索引处的单个输出字符串中。我还允许用户指定此输出字符串的默认内容。如果没有定义字段来替换给定位置,则会输出默认字符

我的问题是,如何才能有效地做到这一点? StringBuilder 类有一个 Insert(int index, string value) 方法,但这每次都会延长输出字符串而不是覆盖它。我是否必须使用 StringBuilder[int index] 索引器一次设置每个字符一个,这样效率低吗?因为我会多次这样做,所以我希望它尽可能快。

谢谢。

In my .NET program I allow a user to define "fields" which are values calculated by the business logic. These fields have a position and length, so that they can all be inserted into a single output string at a given index. I also allow a user to specify default content of this output string. If no field is defined to replace a given position, the default character is output instead

My question is, how can I do this efficiently? The StringBuilder class has an Insert(int index, string value) method, but this lengthens the output string each time rather than overwriting it. Am I going to have to set each char one at a time using the StringBuilder[int index] indexer, and is this inefficient? Since I am going to be doing this a lot of times I would like it to be as fast as possible.

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

⊕婉儿 2024-09-20 17:28:34

一次只做一个角色可能是你最好的选择。我这样说是因为在 StringBuilder 上调用 InsertRemove 会导致字符向右/向左移动,就像任何可变的类似方法一样索引集合,例如 List

也就是说,这是一个很好的候选扩展方法,可以让你的生活更轻松一些。

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.

Doing it one character at a time is likely your best bet. I say this because calling Insert and Remove on a StringBuilder results in characters being shifted right/left, just as the analogous methods would in any mutable indexed collection such as a List<char>.

That said, this is an excellent candidate for an extension method to make your life a bit easier.

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;
}

Usage example:

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

Console.WriteLine(builder.ToString());

Output:

My name is Bob.
赴月观长安 2024-09-20 17:28:34

StringBuilder 类允许您构建可变字符串。在执行插入之前尝试使用Remove函数。由于它是随机访问的,所以速度应该非常快。只要 StringBuilder 保持相同的容量,就不会花费时间在内存中复制字符串。如果您知道字符串会变长,请尝试在调用 New StringBuilder() 时将容量设置得更大

The StringBuilder class lets you build a mutable string. Try using the Remove function before doing the Insert. Since its randomly accessible, it should be very quick. As long as the StringBuilder keeps the same capacity, it won't be taking time copying strings around in memory. If you know the string will become longer, try setting the capacity to be larger when you call New StringBuilder()

狼亦尘 2024-09-20 17:28:34

只要字符串是不可变的,每次对其进行操作都会导致 GC 负载,甚至 StringBuilder 插入/删除调用。
我会通过插入点剪切源字符串,然后用需要插入的数据“压缩”它。
之后,您可以在列表中连接字符串,以获得结果字符串。

这是执行拆分/压缩操作的示例代码。
它假设字段被定义为(位置、长度、值)的元组。

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));
    }
}

顺便说一句,最好使用正则表达式替换特殊用户标记,例如 [价格]、[数量]?

As long, as strings are immuteble, each manipulation with it, will cause GC load, even StringBuilder insert/remove calls.
I would cut source string by insertion points, and then "zip" it with data, that need to be inserted.
After that you can just concat strings inside list, to get resulting string.

Here is a sample code that do split/zip operaions.
It assumes, that Fields are defined as touple of (position, length, value).

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));
    }
}

BTW, would be better to replace special user markers, like [price], [qty] using regex?

晚风撩人 2024-09-20 17:28:34

我建议使用 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/

I would recommend using the StringBuilder class. However you can do it with a string but there can be side effects. Here are a couple blog posts that show how to manipulate strings and the possible side effects.

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/

山人契 2024-09-20 17:28:34

如果替换子字符串将成为一个大瓶颈,您可能想完全放弃子字符串。相反,请将数据分解为可以独立修改的字符串。如下所示:

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

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

这是一个简单的静态示例,但我确信可以使其更加通用,这样如果每个用户定义不同的字段,您就可以处理它。将数据分解为字段后,如果您仍然需要修改字段中的单个字符,至少您不会触及整个数据集。

现在,这可能会将瓶颈推向 OutputDataLine 函数,具体取决于您对数据执行的操作。但如有必要,可以单独处理。

If replacing substrings is going to be a big bottleneck, you may want to ditch the substrings thing altogether. Instead, break up your data into strings that can be independently modified. Something like the following:

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

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

That's a simple static example, but I'm sure that could be made more generic so that if every user defines fields differently you could handle it. After breaking your data into fields, if you still need to modify individual characters in the fields at least you're not touching the whole set of data.

Now, this may push the bottle neck to the OutputDataLine function, depending on what you're doing with the data. But that can be handled separately if necessary.

奶茶白久 2024-09-20 17:28:34

如果您的字符串已经预先格式化为长度,那么 StringBuilder 类

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

刚刚设置了您的起始索引和计数 = 1,以便您可以替换该特定实例。

您可以做的另一件事是使用 String.Format()。将所有预定义字段转换为索引,这样您就会得到一个类似“This {0} is very {1}”的字符串,然后只需将参数与特定索引进行匹配并执行 String.Format(myString, myParams);

——劳尔

If your string is already pre formated for the length then the StringBuilder class has

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

just set your start index and count = 1 so you can replace that specific instance.

Another thing you could do is use String.Format(). Convert all your pre defined fields into indexes so you get a string like "This {0} is very {1}" and then just match up the parameters to the specific index and do a String.Format(myString, myParams);

-Raul

只是在用心讲痛 2024-09-20 17:28:34

正如您公平地说的,StringBuilder 有 Insert 方法,但没有 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 ) );
    }

As you fairly stated, StringBuilder has Insert method but no Overwrite method.

So i have created the Overwrite extension method, see below, for my projects.

Note that it will cut the value if the StringBuilder has not enough room for it. You can easily modify it's logic, however.

    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 ) );
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文