如何通过位置替换字符串的一部分?

213

我有这个字符串:ABCDEFGHIJ

我需要使用字符串ZX替换从第4个位置到第5个位置的字符串。

替换后的结果应该是:ABCZXFGHIJ

但不要使用 string.replace("DE","ZX") 这种方式,我需要使用位置来实现。

我该怎么做呢?


@TotZam - 请检查日期。这个比你链接的那个旧。 - ToolmakerSteve
@ToolmakerSteve 我通常会关注问题和答案的质量,而不是日期,就像这里所说的那样。在这种情况下,我似乎犯了一个错误,点击了错误的重复标记,因为这个问题的质量显然更好,所以我已经标记了另一个问题。 - Tot Zam
@TotZam - 啊,我不知道那个建议 - 谢谢你指出来。尽管看到一些旧的问题被报告为新的重复问题很令人困惑,所以在这种情况下,值得明确解释你正在标记一个更旧的问题为重复,因为链接的问题有更好的答案。 - ToolmakerSteve
22个回答

3
所有其他的答案在字符串包含Unicode字符(如表情符号)时都不起作用,因为一个Unicode字符的字节数比一个普通字符多。

例如:表情符号''转换成字节后,相当于2个字符的大小。因此,如果Unicode字符位于字符串开头,offset参数将被移动。

通过这个主题,我扩展了StringInfo类来保持Nick Miller算法的替换位置,以避免这种情况发生:

public static class StringInfoUtils
{
    public static string ReplaceByPosition(this string str, string replaceBy, int offset, int count)
    {
        return new StringInfo(str).ReplaceByPosition(replaceBy, offset, count).String;
    }

    public static StringInfo ReplaceByPosition(this StringInfo str, string replaceBy, int offset, int count)
    {
        return str.RemoveByTextElements(offset, count).InsertByTextElements(offset, replaceBy);
    }

    public static StringInfo RemoveByTextElements(this StringInfo str, int offset, int count)
    {
        return new StringInfo(string.Concat(
            str.SubstringByTextElements(0, offset),
            offset + count < str.LengthInTextElements
                ? str.SubstringByTextElements(offset + count, str.LengthInTextElements - count - offset)
                : ""
            ));
    }
    public static StringInfo InsertByTextElements(this StringInfo str, int offset, string insertStr)
    {
        if (string.IsNullOrEmpty(str?.String))
            return new StringInfo(insertStr);
        return new StringInfo(string.Concat(
            str.SubstringByTextElements(0, offset),
            insertStr,
            str.LengthInTextElements - offset > 0 ? str.SubstringByTextElements(offset, str.LengthInTextElements - offset) : ""
        ));
    }
}

https://dotnetfiddle.net/l0dqS5 我无法让其他Unicode方法失败。请更改示例以展示您的用例。非常期待结果。 - Markus
1
@MarkusHooge 尝试将 Unicode 字符放在字符串开头,例如:https://dotnetfiddle.net/3V2K3Y。你会发现只有最后一行能正常工作,并将字符放在第四个位置(否则,Unicode 字符将占用 2 个字符长度)。 - GGO
你说得对,这不太清楚。我更新了我的答案,加入了更多解释为什么其他的答案不起作用。 - GGO

3
string myString = "ABCDEFGHIJ";
string modifiedString = new StringBuilder(myString){[3]='Z', [4]='X'}.ToString();

让我解释一下我的解决方案。
假设有一个问题陈述,要在字符串的两个特定位置(“位置4到位置5”)中使用两个字符‘Z’和‘X’进行更改,并且要求使用位置索引来更改字符串,而不是使用字符串的Replace()方法(可能是因为实际字符串中某些字符可能重复),我更喜欢使用简约方法来实现目标,而不是使用Substring()、string Concat()或string Remove()和Insert()方法。虽然所有这些解决方案都可以达到同样的目的,但它只取决于个人选择和哲学上采用简约方法的方式。
回到我上面提到的解决方案,如果我们仔细观察字符串和StringBuilder,它们都将给定的字符串内部视为字符数组。如果我们查看StringBuilder的实现,它将维护一个内部变量,类似于“internal char [] m_ChunkChars;”,以捕获给定的字符串。由于这是一个内部变量,我们无法直接访问它。对于外部世界,为了能够访问和更改该字符数组,StringBuilder通过索引器属性将其公开,如下所示:
    [IndexerName("Chars")]
    public char this[int index]
    {
      get
      {
        StringBuilder stringBuilder = this;
        do
        {
          // … some code
            return stringBuilder.m_ChunkChars[index1];
          // … some more code
        }
      }
      set
      {
        StringBuilder stringBuilder = this;
        do
        {
            //… some code
            stringBuilder.m_ChunkChars[index1] = value;
            return;
            // …. Some more code
        }
      }
    }

我的解决方案利用了这个索引器的能力,直接改变内部维护的字符数组,我认为这是高效且简约的。

顺便说一句,我们可以更详细地重写上面的解决方案,例如:

 string myString = "ABCDEFGHIJ";
 StringBuilder tempString = new StringBuilder(myString);
 tempString[3] = 'Z';
 tempString[4] = 'X';
 string modifiedString = tempString.ToString();

在这种情况下,我还想提到的是,在string的情况下,它也有索引器属性来公开其内部字符数组,但在这种情况下,它只有Getter属性(没有Setter),因为字符串是不可变的。这就是为什么我们需要使用StringBuilder来修改字符数组。

[IndexerName("Chars")]
public extern char this[int index] { [SecuritySafeCritical, __DynamicallyInvokable, MethodImpl(MethodImplOptions.InternalCall)] get; }

最后但并非最不重要的是,这个解决方案只适用于特定问题,其中要求仅在已知位置索引上替换少量字符。当需求是改变相对较长的字符串时,即需要改变的字符数量很多时,它可能不是最佳选择。


1
请编辑您的答案,解释这段代码如何回答问题。 - tshimkus

3

借助本文,我创建了以下带有额外长度检查的函数。

public string ReplaceStringByIndex(string original, string replaceWith, int replaceIndex)
{
    if (original.Length >= (replaceIndex + replaceWith.Length))
    {
        StringBuilder rev = new StringBuilder(original);
        rev.Remove(replaceIndex, replaceWith.Length);
        rev.Insert(replaceIndex, replaceWith);
        return rev.ToString();
    }
    else
    {
        throw new Exception("Wrong lengths for the operation");
    }
}

2

我正在寻找一个具备以下要求的解决方案:

  1. 只使用单个一行表达式
  2. 只使用系统内置方法(不使用自定义实用程序)

解决方案1

最适合我的解决方案是:

// replace `oldString[i]` with `c`
string newString = new StringBuilder(oldString).Replace(oldString[i], c, i, 1).ToString();

这里使用了StringBuilder.Replace(oldChar, newChar, position, count)

解决方案2

另一个满足我的需求的解决方案是使用字符串的Substring和连接:

string newString = oldStr.Substring(0, i) + c + oldString.Substring(i+1, oldString.Length);

这也是可以的。我猜效率上来说不如第一个(因为存在不必要的字符串拼接)。但是“过早优化是万恶之源”。

所以选择你最喜欢的吧 :)


方案2可行,但需要更正: string newString = oldStr.Substring(0, i) + c + oldString.Substring(i+1, oldString.Length-(i+1)); - Yura G
更好的方法是,只需使用string newString = oldStr.Substring(0, i) + c + oldString.Substring(i+1)。默认情况下,如果没有指定长度,.Substring将提取字符串的其余部分。 - Sloan Reynolds

1

你好,这段代码对我很有帮助:

var theString = "ABCDEFGHIJ";
var aStringBuilder = new StringBuilder(theString);
aStringBuilder.Remove(3, 2);
aStringBuilder.Insert(3, "ZX");
theString = aStringBuilder.ToString();

1
假设我们知道要替换的字符串的索引。
    string s = "ABCDEFGDEJ";
    string z = "DE";
    int i = s.IndexOf(z);
    if(i == 3)
        s = s.Remove(3,z.Length).Insert(3,"ZX");
    //s = ABCZXFGDEJ

0

我这样做

Dim QTT As Double
                If IsDBNull(dr.Item(7)) Then
                    QTT = 0
                Else
                    Dim value As String = dr.Item(7).ToString()
                    Dim posicpoint As Integer = value.LastIndexOf(".")
                    If posicpoint > 0 Then
                        Dim v As New Text.StringBuilder(value)
                        v.Remove(posicpoint, 1)
                        v.Insert(posicpoint, ",")
                        QTT = Convert.ToDouble(v.ToString())
                    Else
                        QTT = Convert.ToDouble(dr.Item(7).ToString())
                    End If
                    Console.WriteLine(QTT.ToString())
                End If

0

这是一个简单的扩展方法:

    public static class StringBuilderExtensions
    {
        public static StringBuilder Replace(this StringBuilder sb, int position, string newString)
            => sb.Replace(position, newString.Length, newString);

        public static StringBuilder Replace(this StringBuilder sb, int position, int length, string newString)
            => (newString.Length <= length)
                ? sb.Remove(position, newString.Length).Insert(position, newString)
                : sb.Remove(position, length).Insert(position, newString.Substring(0, length));
    }

使用方法如下:

var theString = new string(' ', 10);
var sb = new StringBuilder(theString);
sb.Replace(5, "foo");
return sb.ToString();

0
String timestamp = "2019-09-18 21.42.05.000705";
String sub1 = timestamp.substring(0, 19).replace('.', ':'); 
String sub2 = timestamp.substring(19, timestamp.length());
System.out.println("Original String "+ timestamp);      
System.out.println("Replaced Value "+ sub1+sub2);

0
你可以使用这个工具。
编辑:我修复了代码,现在它真的可以工作了,感谢你指出问题。另外,source[...start] 返回的是从索引0到start(不包括start)的source子字符串,source[end...]也是类似的,只不过是从end到字符串末尾。更多关于范围的信息在这里。
(顺便说一句,这也适用于数组和列表)
public static class ForStrings
{
    public static string ReplaceAt(this string source, int start, int end, string replacement)
    {
        return source[..start] + replacement + source[end..];
    }
}

使用方法:

string OPsString = "ABCDEFGHIJ";
string newString = OPsString.ReplaceAt(3, 5, "ZX");
Console.WriteLine(newString); //"ABCZXFGHIJ"

编辑部分(在您的编辑中Edit:我修复了代码...不需要也,范围与列表不兼容 "List<T>支持索引但不支持范围。")不需要。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接