Jak zamienić część ciągu na pozycję?

155

Mam ten ciąg: ABCDEFGHIJ

Muszę zamienić sznurkiem z pozycji 4 na 5 ZX

Będzie to wyglądać tak: ABCZXFGHIJ

Ale nie do używania z string.replace("DE","ZX")- muszę użyć z pozycją

Jak mogę to zrobić?

Gali
źródło
@TotZam - proszę sprawdzić daty. Ten jest starszy niż ten, z którym łączysz się.
ToolmakerSteve
@ToolmakerSteve Zwykle patrzę na jakość pytań i odpowiedzi, a nie na daty, jak powiedziano tutaj . W tym przypadku wydaje mi się, że popełniłem błąd i kliknąłem niewłaściwy, aby oznaczyć jako zduplikowane, ponieważ jakość tego pytania jest oczywiście lepsza, dlatego też oznaczyłem drugie pytanie.
Tot Zam
@TotZam - ach, nie wiedziałem o tej rekomendacji - dziękuję za wskazanie. (Chociaż mylące jest zobaczenie czegoś starszego zgłoszonego jako duplikat czegoś nowszego, więc w takim przypadku warto byłoby wyraźnie wyjaśnić, że oznaczasz jako duplikat pytanie STARSZE, ponieważ link ma lepsze odpowiedzi.)
ToolmakerSteve

Odpowiedzi:

198

Najłatwiejszym sposobem dodawania i usuwania zakresów w ciągu jest użycie rozszerzenia StringBuilder.

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

Alternatywą jest użycie String.Substring , ale myślę, że StringBuilderkod staje się bardziej czytelny.

Albin Sunnanbo
źródło
6
byłoby lepiej
zawinąć
Dlaczego używasz tutaj StringBuilder? Sprawdź odpowiedź V4Vendetta, która jest znacznie bardziej czytelna ...
Fortega
1
@Fortega Remove () i Insert () tworzą i zwracają nowy ciąg. Podejście StringBuilder pozwala uniknąć tego dodatkowego kosztu, pracując w wstępnie przydzielonej sekcji pamięci i przenosząc w niej znaki.
redcalx
@redcalx To prawda, ale wprowadzono dodatkowy koszt, wprowadzając obiekt StringBuilder i zmniejszając czytelność
Fortega
2
Jaki jest sens używania StringBuilder tutaj, jeśli wywołujesz Remove i Insert w taki sam sposób jak V4Vendetta, ale robi to bezpośrednio na łańcuchu? Wydaje się, że kod jest zbędny.
metabuddy
186
string s = "ABCDEFGH";
s= s.Remove(3, 2).Insert(3, "ZX");
V4Vendetta
źródło
17
Wolę to od inicjowania nowego StringBuildera (...). Dzięki!
Michael Norgren
3
Nie wiem, dlaczego ktoś miałby używać do tej operacji stringbuildera. To dobra odpowiedź
NappingRabbit
43

ReplaceAt (int index, int length, string replace)

Oto metoda rozszerzenia, która nie używa StringBuilder ani Substring. Ta metoda pozwala również na wydłużenie ciągu zastępczego poza długość ciągu źródłowego.

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

Jeśli używasz tej funkcji, jeśli chcesz, aby cały zastępczy ciąg zastąpił jak najwięcej znaków, ustaw długość na długość zastępującego ciągu:

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

W przeciwnym razie możesz określić liczbę znaków, które zostaną usunięte:

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

Jeśli określisz długość na 0, ta funkcja działa tak samo jak funkcja wstawiania:

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

Wydaje mi się, że jest to bardziej wydajne, ponieważ klasa StringBuilder nie musi być inicjowana i ponieważ używa bardziej podstawowych operacji. Proszę, popraw mnie jeśli się mylę. :)

Nicholas Miller
źródło
5
Stringbuilder jest bardziej wydajny, jeśli łańcuch jest dłuższy niż, powiedzmy, 200 znaków.
Tim Schmelter,
Bez stresu. Pracowałem prosto. Dzięki
D_Edet
9

Użyj String.Substring()(szczegóły tutaj ), aby wyciąć lewą część, następnie zamiennik, a następnie prawą część. Graj z indeksami, aż wszystko będzie dobrze :)

Coś jak:

string replacement=original.Substring(0,start)+
    rep+original.Substring(start+rep.Length);
Daniel Mošmondor
źródło
2
Stworzyłem trzy metody przy użyciu powyższych trzech metod (string.Remove / Insert, Stringbuilder.Remove / Insert i odpowiedź Daniel's Substring, a SubString był najszybszą metodą.
Francine DeGrood Taylor
7

Jako metoda rozszerzenia.

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();
    }
}
Ali Khalid
źródło
5
        string s = "ABCDEFG";
        string t = "st";
        s = s.Remove(4, t.Length);
        s = s.Insert(4, t);
Javed Akram
źródło
To działa, prawdopodobnie nie chcesz odkładać tponownie.
Pierre
4

Jeśli zależy Ci na wydajności, to czego chcesz tutaj uniknąć, są alokacje. A jeśli jesteś na .NET Rdzenia 2.1+ (lub, jak dotąd niepublikowany, .Net standardowe 2,1), a następnie można, korzystając z string.Createmetody :

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

To podejście jest trudniejsze do zrozumienia niż alternatywy, ale jest to jedyne, które przydzieli tylko jeden obiekt na wywołanie: nowo utworzony ciąg.

svick
źródło
3

Możesz spróbować czegoś, co łączy to:

string str = "ABCDEFGHIJ";
str = str.Substring(0, 2) + "ZX" + str.Substring(5);
MBU
źródło
3

Tak jak inni wspomnieli, Substring()funkcja jest tam z jakiegoś powodu:

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

Również zmierzyłem to w stosunku do StringBuilderrozwiązania i otrzymałem 900 tików w porównaniu z 875. Jest to więc nieco wolniejsze.

John Alexiou
źródło
@ Aaron.S - nie, nie. W moim przykładzie "ABCDEFGHIJ"i "ZX"mają różne długości.
John Alexiou
3

Jeszcze inny

    public static string ReplaceAtPosition(this string self, int position, string newValue)        
    {
        return self.Remove(position, newValue.Length).Insert(position, newValue); 
    }
Stijn Van Antwerpen
źródło
2

Z pomocą tego posta tworzę następującą funkcję z dodatkowymi kontrolami długości

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");
    }
}
Hassan
źródło
2

Wszystkie inne odpowiedzi nie działają, jeśli ciąg zawiera znak Unicode (jak emotikony), ponieważ znak Unicode ma więcej bajtów niż znak.

Przykład : emoji „🎶” przekonwertowane na bajty waży odpowiednik 2 znaków. Tak więc, jeśli znak Unicode zostanie umieszczony na początku ciągu, offsetparametr zostanie przesunięty).

W tym temacie rozszerzam klasę StringInfo na Replace by position, zachowując algorytm Nicka Millera, aby tego uniknąć:

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) : ""
        ));
    }
}
GGO
źródło
dotnetfiddle.net/l0dqS5 Nie udało mi się doprowadzić do niepowodzenia innych metod w przypadku Unicode. Zmień skrzypce, aby zademonstrować swój przypadek użycia. Bardzo zainteresowany wynikami.
Markus
1
@MarkusHooge Spróbuj umieścić znaki Unicode na początku swojego ciągu, na przykład: dotnetfiddle.net/3V2K3Y . Zobaczysz, że tylko ostatnia linia działa dobrze i umieść znak na 4 miejscu (w innym przypadku znak Unicode zajmuje 2 znaki)
GGO
Masz rację, to nie jest jasne. Zaktualizowałem swoją odpowiedź, aby dodać więcej wyjaśnień, dlaczego inne odpowiedzi nie działają
GGO
1

Szukałem rozwiązania spełniającego następujące wymagania:

  1. użyj tylko jednego, jednowierszowego wyrażenia
  2. używaj tylko metod wbudowanych w system (brak niestandardowego narzędzia)

Rozwiązanie 1

Rozwiązanie, które najbardziej mi odpowiada, to:

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

To używa StringBuilder.Replace(oldChar, newChar, position, count)

Rozwiązanie 2

Innym rozwiązaniem, które spełnia moje wymagania, jest użycie Substringz konkatenacją:

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

To też jest w porządku. Wydaje mi się, że nie jest to tak wydajne, jak pierwsze wykonanie (z powodu niepotrzebnej konkatenacji ciągów). Ale przedwczesna optymalizacja jest źródłem wszelkiego zła .

Wybierz więc ten, który najbardziej Ci się podoba :)

KFL
źródło
Rozwiązanie 2 działa, ale wymaga korekty: string newString = oldStr.Substring (0, i) + c + oldString.Substring (i + 1, oldString.Length- (i + 1));
Yura G,
0

Lepiej jest użyć String.substr().

Lubię to:

ReplString = GivenStr.substr(0, PostostarRelStr)
           + GivenStr(PostostarRelStr, ReplString.lenght());
Varun Kumar
źródło
0
string myString = "ABCDEFGHIJ";
string modifiedString = new StringBuilder(myString){[3]='Z', [4]='X'}.ToString();

Pozwól mi wyjaśnić moje rozwiązanie.
Biorąc pod uwagę stwierdzenie problemu polegające na zmianie łańcucha w jego dwóch określonych pozycjach („pozycja 4 do pozycji 5”) z dwoma znakami „Z” i „X”, a prośba o użycie indeksu pozycji do zmiany ciągu, a nie łańcucha Zamień ( ) (może to wynikać z możliwości powtórzenia się niektórych znaków w rzeczywistym ciągu), wolałbym zastosować minimalistyczne podejście do osiągnięcia celu niż użycie Substring()i string Concat()lub string Remove()i Insert()podejście. Choć wszystkie te rozwiązania będą służyć temu samemu celowi, zależy to tylko od osobistego wyboru i filozofii rozliczenia się z minimalistycznym podejściem.
Wracając do mojego rozwiązania, wspomnij powyżej, jeśli przyjrzymy się bliżej stringiStringBuilder, oba z nich wewnętrznie traktują dany ciąg jako tablicę znaków. Jeśli spojrzymy na implementacjęStringBuilderutrzymuje wewnętrzną zmienną, taką jak „ internal char[] m_ChunkChars;”, przechwytującą dany ciąg. Ponieważ jest to zmienna wewnętrzna, nie możemy bezpośrednio uzyskać do niej dostępu. Aby świat zewnętrzny mógł uzyskać dostęp i zmienić tę tablicę znaków, StringBuilderuwidacznia je za pomocą właściwości indeksatora, która wygląda jak poniżej

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

Moje rozwiązanie wspomniane powyżej wykorzystuje tę możliwość indeksowania do bezpośredniej zmiany wewnętrznie utrzymywanej tablicy znaków, która IMO jest wydajna i minimalistyczna.

BTW; powyższe rozwiązanie możemy przepisać dokładniej, jak poniżej

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

W tym kontekście chciałbym również wspomnieć, że w przypadku tego stringma również właściwość indeksatora jako sposób na ujawnienie swojej wewnętrznej tablicy znaków, ale w tym przypadku ma tylko właściwość Getter (i nie ma Settera), ponieważ string jest niezmienny z natury. I dlatego musimy użyć, StringBuilderaby zmienić tablicę znaków.

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

I wreszcie, to rozwiązanie jest najlepsze tylko dla tego konkretnego problemu, w którym prośba o zastąpienie tylko kilku znaków z góry znanym indeksem pozycji. Może nie być najlepszym rozwiązaniem, gdy wymagana jest zmiana dość długiego ciągu, tj. Liczba znaków do zmiany jest duża.

Jak gdyby
źródło
1
Edytuj swoją odpowiedź, aby wyjaśnić, w jaki sposób ten kod odpowiada na pytanie
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);
user1355816
źródło
0

Oto prosta metoda rozszerzenia:

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

Użyj tego w ten sposób:

var theString = new string(' ', 10);
var sb = new StringBuilder(theString);
sb.Replace(5, "foo");
return sb.ToString();
MovGP0
źródło
-2

Myślę, że najprostszym sposobem byłoby to: (bez stringbuildera)

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

To działa, ponieważ zmienna typu string może być traktowana jako tablica zmiennych typu char. Możesz na przykład odnieść się do drugiego znaku zmiennej łańcuchowej o nazwie „myString” jako myString [1]

Sfou
źródło
To działa tylko dlatego, że w przykładzie autora stringznaki, które mają być zastąpione, występują tylko raz. Jeśli napotkasz to, powiedzmy, "DBCDEFGHIE"otrzymasz "ZBCZXFGHIX", co nie jest pożądanym rezultatem. Nie zgodziłbym się również, że jest to prostsze niż inne nie- StringBuilderrozwiązania, które zostały opublikowane dwa i pół roku temu.
BACON