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ć?
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ć?
Odpowiedzi:
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.
string s = "ABCDEFGH";
s= s.Remove(3, 2).Insert(3, "ZX");
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ę. :)
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);
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();
}
}
string s = "ABCDEFG";
string t = "st";
s = s.Remove(4, t.Length);
s = s.Insert(4, t);
tponownie.
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.
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.
"ABCDEFGHIJ"i "ZX"mają różne długości.
Jeszcze inny
public static string ReplaceAtPosition(this string self, int position, string newValue)
{
return self.Remove(position, newValue.Length).Insert(position, newValue);
}
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");
}
}
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) : ""
));
}
}
Szukałem rozwiązania spełniającego następujące wymagania:
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)
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 :)
Lepiej jest użyć String.substr().
Lubię to:
ReplString = GivenStr.substr(0, PostostarRelStr)
+ GivenStr(PostostarRelStr, ReplString.lenght());
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.
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);
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();
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]
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.