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


155

我有这个字符串: ABCDEFGHIJ

我需要将字符串从位置4替换为位置5 ZX

它看起来像这样: ABCZXFGHIJ

但不要与string.replace("DE","ZX")-我需要与位置一起使用

我该怎么做?


@TotZam-请检查日期。链接比您链接的链接早。
ToolmakerSteve

@ToolmakerSteve我通常会看问题和答案的质量,而不是像这里所说的那样看日期。在这种情况下,我似乎犯了一个错误,并单击了错误的一个以将其标记为重复,因为此问题的质量明显更好,因此我标记了另一个问题。
Tot Zam's

@TotZam-啊,我不知道那个建议-感谢您指出。(尽管将较旧的内容报告为较新的内容是令人困惑的,所以在这种情况下,有必要明确说明您将一个较旧的问题标记为重复,因为链接的问题具有更好的答案。)
ToolmakerSteve

Answers:


198

添加和删​​除字符串范围的最简单方法是使用StringBuilder

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

一种替代方法是使用String.Substring,但是我认为StringBuilder代码更具可读性。


6
最好用扩展方法将其包装起来:)
balexandre

为什么在这里使用StringBuilder?检查V4仇杀队的答案,这更易读...
Fortega

1
@Fortega Remove()和Insert()分别创建并返回一个新字符串。StringBuilder方法通过在内存的预分配部分中处理并在其中移动字符来避免这种额外的开销。
redcalx18年

@redcalx是的,但是通过引入StringBuilder对象并降低可读性引入了额外的费用
Fortega,2016年

2
如果您以与V4Vendetta相同的方式调用Remove和Insert,但在字符串上直接执行此操作,那么在这里使用StringBuilder有什么意义呢?似乎是冗余代码。
metabuddy '18

186
string s = "ABCDEFGH";
s= s.Remove(3, 2).Insert(3, "ZX");

17
我不喜欢初始化新的StringBuilder(...)。谢谢!
Michael Norgren'5

3
我不知道为什么有人会使用stringbuilder进行此操作。这是一个很好的答案
NappingRabbit

43

ReplaceAt(int索引,int长度,字符串替换)

这是一个不使用StringBuilder或Substring的扩展方法。此方法还允许替换字符串延伸超过源字符串的长度。

//// str - the source string
//// index- the start location to replace at (0-based)
//// length - the number of characters to be removed before inserting
//// replace - the string that is replacing characters
public static string ReplaceAt(this string str, int index, int length, string replace)
{
    return str.Remove(index, Math.Min(length, str.Length - index))
            .Insert(index, replace);
}

使用此功能时,如果要整个替换字符串替换尽可能多的字符,则将length设置为替换字符串的长度:

"0123456789".ReplaceAt(7, 5, "Hello") = "0123456Hello"

否则,您可以指定将要删除的字符数:

"0123456789".ReplaceAt(2, 2, "Hello") = "01Hello456789"

如果将长度指定为0,则此函数的作用类似于insert函数:

"0123456789".ReplaceAt(4, 0, "Hello") = "0123Hello456789"

我猜这会更有效,因为不需要初始化StringBuilder类,并且因为它使用了更多基本操作。如果我错了,请纠正我。:)


5
如果字符串长于200个字符,则Stringbuilder效率更高。
蒂姆·施密特

无压力。一直努力。谢谢
D_Edet

9

使用String.Substring()此处的详细信息)先切割左部分,然后切割替换物,再切割右部分。使用索引直到正确为止:)

就像是:

string replacement=original.Substring(0,start)+
    rep+original.Substring(start+rep.Length);

2
我创建使用上述三种方法的三种方法(string.Remove /插入,Stringbuilder.Remove /插入和丹尼尔的子串的答案,串是最快的方法。
弗朗辛DeGrood泰勒

7

作为扩展方法。

public static class StringBuilderExtension
{
    public static string SubsituteString(this string OriginalStr, int index, int length, string SubsituteStr)
    {
        return new StringBuilder(OriginalStr).Remove(index, length).Insert(index, SubsituteStr).ToString();
    }
}


4

如果您关心性能,那么这里要避免的事情就是分配。如果您使用的是.Net Core 2.1+(或尚未发布的.Net Standard 2.1),则可以使用以下string.Create方法

public static string ReplaceAt(this string str, int index, int length, string replace)
{
    return string.Create(str.Length - length + replace.Length, (str, index, length, replace),
        (span, state) =>
        {
            state.str.AsSpan().Slice(0, state.index).CopyTo(span);
            state.replace.AsSpan().CopyTo(span.Slice(state.index));
            state.str.AsSpan().Slice(state.index + state.length).CopyTo(span.Slice(state.index + state.replace.Length));
        });
}

这种方法比其他方法更难理解,但它是唯一在每次调用中仅分配一个对象的方法:新创建的字符串。


3

您可以尝试以下链接:

string str = "ABCDEFGHIJ";
str = str.Substring(0, 2) + "ZX" + str.Substring(5);

3

就像其他人提到的Substring()功能存在是有原因的:

static void Main(string[] args)
{
    string input = "ABCDEFGHIJ";

    string output = input.Overwrite(3, "ZX"); // 4th position has index 3
    // ABCZXFGHIJ
}

public static string Overwrite(this string text, int position, string new_text)
{
    return text.Substring(0, position) + new_text + text.Substring(position + new_text.Length);
}

此外,我这个定时对StringBuilder解决方案,得到了900次抽搐与875.所以这是稍微慢一些。


@ Aaron.S-不,不是。在我的示例中"ABCDEFGHIJ""ZX"具有不同的长度。
John Alexiou

3

完后还有

    public static string ReplaceAtPosition(this string self, int position, string newValue)        
    {
        return self.Remove(position, newValue.Length).Insert(position, newValue); 
    }

2

在这篇文章的帮助下,我创建了以下函数,并进行了额外的长度检查

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

如果字符串包含Unicode字符(如Emojis),则所有其他答案均不起作用,因为Unicode字符比字符重一个字节。

示例:将表情符号“🎶”转换为字节后,其权重将等于2个字符。因此,如果将Unicode字符放置在字符串的开头,则offset参数将被移位。

本主题中,我将StringInfo类扩展为Replace by按位置保留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) : ""
        ));
    }
}

dotnetfiddle.net/l0dqS5我无法获得其他方法使Unicode失败。请更改小提琴以演示您的用例。对结果非常感兴趣。
马库斯

1
@MarkusHooge尝试将unicode字符放在字符串的开头,例如:dotnetfiddle.net/3V2K3Y。您会看到只有最后一行有效,并将char置于第四位(在另一种情况下,unicode char占用2个char长度)
GGO

您说得对,目前尚不清楚。我更新了我的答案,以添加更多解释,为什么其他答案不起作用
GGO

1

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

  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有效,但需要更正:字符串newString = oldStr.Substring(0,i)+ c + oldString.Substring(i + 1,oldString.Length-(i + 1));
Yura G


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

让我解释一下我的解决方案。
给定问题说明,用两个字符“ Z”和“ X”在两个特定位置更改字符串(“位置4至位置5”),要求的是使用位置索引来更改字符串,而不是字符串Replace( )方法(可能是由于实际字符串中可能重复某些字符),我宁愿使用极简主义的方法来实现目标,而不是使用Substring()和字符串Concat()或字符串Remove()Insert()方法。尽管所有这些解决方案都将达到相同的目的,但是这仅取决于个人选择和以最小化方法解决的哲学。
回到上面我提到的解决方案中,如果我们仔细看一下stringStringBuilder,它们在内部都将给定的字符串视为字符数组。如果我们看一下StringBuilder它的实现,它将维护一个内部变量,例如“ internal char[] m_ChunkChars;”,以捕获给定的字符串。现在,由于这是一个内部变量,因此我们无法直接访问该变量。对于外部世界,要能够访问和更改该字符数组,请StringBuilder通过如下所示的indexer属性将其公开

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

上面提到的我的解决方案利用此索引器功能来直接更改IMO有效且极简的内部维护字符数组。

BTW; 我们可以更详细地重写上面的解决方案,如下所示

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

在这种情况下,string我们还要提及的是,它还具有indexer属性作为公开其内部字符数组的一种方式,但是在这种情况下,它仅具有Getter属性(而没有Setter),因为字符串本质上是不可变的。这就是为什么我们需要使用StringBuilder更改字符数组的原因。

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

最后但并非最不重要的一点是,此解决方案仅最适合此特定问题,在该问题中,要求仅用已知的位置索引预先替换几个字符。当要求更改相当长的字符串(即要更改的字符数很大)时,它可能不是最合适的选择。


1
请编辑您的答案以解释该代码如何回答问题
tshimkus

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

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

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

-2

我相信最简单的方法是这样的:(没有stringbuilder)

string myString = "ABCDEFGHIJ";
char[] replacementChars = {'Z', 'X'};
byte j = 0;

for (byte i = 3; i <= 4; i++, j++)  
{                   
myString = myString.Replace(myString[i], replacementChars[j]);  
}

之所以可行,是因为可以将string类型的变量视为char变量数组。例如,您可以将名称为“ myString”的字符串变量的第二个字符称为myString [1]。


这仅起作用,因为在作者的示例中string,每个要替换的字符仅出现一次。例如,如果您对此进行测试,"DBCDEFGHIE"则会得到"ZBCZXFGHIX",这不是期望的结果。另外,我不同意这比StringBuilder两年半前发布的其他非解决方案更简单。
培根
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.