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 StringBuilder
kod 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);
t
ponownie.
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.Create
metody :
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 StringBuilder
rozwią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,
offset
parametr 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 Substring
z 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 string
iStringBuilder
, oba z nich wewnętrznie traktują dany ciąg jako tablicę znaków. Jeśli spojrzymy na implementacjęStringBuilder
utrzymuje 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, StringBuilder
uwidacznia 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 string
ma 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ć, StringBuilder
aby 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]
string
znaki, 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- StringBuilder
rozwiązania, które zostały opublikowane dwa i pół roku temu.