C # - Najprostszy sposób na usunięcie pierwszego wystąpienia podciągu z innego ciągu

88

Muszę usunąć pierwsze (i TYLKO pierwsze) wystąpienie ciągu z innego ciągu.

Oto przykład zastępujący ciąg "\\Iteration". To:

ProjectName \\ Iteration \\ Release1 \\ Iteration1

stałoby się:

ProjectName \\ Release1 \\ Iteration1

Oto kod, który to robi:

const string removeString = "\\Iteration";
int index = sourceString.IndexOf(removeString);
int length = removeString.Length;
String startOfString = sourceString.Substring(0, index);
String endOfString = sourceString.Substring(index + length);
String cleanPath = startOfString + endOfString;

Wydaje się, że to dużo kodu.

Moje pytanie brzmi więc: czy istnieje bardziej przejrzysty / czytelniejszy / bardziej zwięzły sposób, aby to zrobić?

Vaccano
źródło

Odpowiedzi:

155
int index = sourceString.IndexOf(removeString);
string cleanPath = (index < 0)
    ? sourceString
    : sourceString.Remove(index, removeString.Length);
LukeH
źródło
10
Ta odpowiedź może się zepsuć w przypadku ciągów zawierających znaki spoza zestawu ASCII. Na przykład w kulturze en-US æi aesą uważane za równe. Próba usunięcia paediaz Encyclopædiaspowoduje wyświetlenie znaku ArgumentOutOfRangeException, ponieważ próbujesz usunąć 6 znaków, gdy pasujący podciąg zawiera tylko 5.
Douglas
6
Możemy to zmodyfikować w ten sposób: sourceString.IndexOf(removeString, StringComparison.Ordinal)aby uniknąć wyjątku.
Borislav Ivanov
30
string myString = sourceString.Remove(sourceString.IndexOf(removeString),removeString.Length);

EDYCJA: @OregonGhost ma rację. Sam bym rozbijał skrypt warunkowymi, aby sprawdzić takie wystąpienie, ale działałem przy założeniu, że ciągi znaków zostały określone jako należące do siebie przez jakiś wymóg. Jest możliwe, że reguły obsługi wyjątków wymagane przez firmę powinny wychwycić tę możliwość. Sam użyłbym kilku dodatkowych wierszy, aby przeprowadzić testy warunkowe, a także uczynić go nieco bardziej czytelnym dla młodszych programistów, którzy mogą nie mieć czasu, aby przeczytać go wystarczająco dokładnie.

Joel Etherton
źródło
9
Spowoduje to awarię, jeśli removeString nie jest zawarta w sourceString.
OregonGhost
27
sourceString.Replace(removeString, "");
malcolm waldron
źródło
18
String.Replace mówi, że „ [r] zwraca nowy ciąg, w którym wszystkie wystąpienia określonego ciągu w bieżącej instancji są zastępowane innym określonym ciągiem ”. PO chciał zastąpić pierwsze wystąpienie.
Wai Ha Lee
6
Powinieneś także trochę wyjaśnić swoją odpowiedź, ponieważ odpowiedzi zawierające tylko kod są niedopuszczalne. Przyjrzyj się pozostałym odpowiedziom i porównaj je ze swoimi, aby uzyskać wskazówki.
Wai Ha Lee
11

Napisałem w tym celu szybki test TDD

    [TestMethod]
    public void Test()
    {
        var input = @"ProjectName\Iteration\Release1\Iteration1";
        var pattern = @"\\Iteration";

        var rgx = new Regex(pattern);
        var result = rgx.Replace(input, "", 1);

        Assert.IsTrue(result.Equals(@"ProjectName\Release1\Iteration1"));
    }

rgx.Replace (wejście, "", 1); mówi, aby szukać w danych wejściowych czegokolwiek pasującego do wzorca, z "", 1 raz.

CaffGeek
źródło
2
W ten sposób rozwiązałeś problem. Wystarczy wziąć pod uwagę wydajność podczas korzystania z wyrażenia regularnego w przypadku takiego problemu.
Thomas
7

Możesz użyć metody rozszerzenia dla zabawy. Zazwyczaj nie polecam dołączania metod rozszerzających do takiej klasy ogólnego przeznaczenia, jak string, ale jak powiedziałem, jest to zabawne. Pożyczyłem odpowiedź od @ Luke'a, ponieważ nie ma sensu ponownie wymyślać koła.

[Test]
public void Should_remove_first_occurrance_of_string() {

    var source = "ProjectName\\Iteration\\Release1\\Iteration1";

    Assert.That(
        source.RemoveFirst("\\Iteration"),
        Is.EqualTo("ProjectName\\Release1\\Iteration1"));
}

public static class StringExtensions {
    public static string RemoveFirst(this string source, string remove) {
        int index = source.IndexOf(remove);
        return (index < 0)
            ? source
            : source.Remove(index, remove.Length);
    }
}
Mike Valenty
źródło
3
Dlaczego zazwyczaj nie zaleca się dołączania metod rozszerzających do takiej klasy ogólnego przeznaczenia, takiej jak String? Jakie są oczywiste wady tego?
Teun Kooijman
1
Łatwo jest zbudować metodę rozszerzającą do celu zbyt specyficznego, aby mieć ją w takiej klasie ogólnego przeznaczenia. Na przykład IsValidIBAN(this string input)byłoby zbyt szczegółowe, aby mieć go na łańcuchu.
Squirrelkiller
3

Jeśli potrzebujesz prostej metody rozwiązania tego problemu. (Może być używany jako rozszerzenie)

Zobacz poniżej:

    public static string RemoveFirstInstanceOfString(this string value, string removeString)
    {
        int index = value.IndexOf(removeString, StringComparison.Ordinal);
        return index < 0 ? value : value.Remove(index, removeString.Length);
    }

Stosowanie:

    string valueWithPipes = "| 1 | 2 | 3";
    string valueWithoutFirstpipe = valueWithPipes.RemoveFirstInstanceOfString("|");
    //Output, valueWithoutFirstpipe = " 1 | 2 | 3";

Zainspirowana i zmodyfikowana odpowiedzią @ LukeH i @ Mike.

Nie zapomnij o StringComparison.Ordinal, aby zapobiec problemom z ustawieniami kultury. https://www.jetbrains.com/help/resharper/2018.2/StringIndexOfIsCultureSpecific.1.html

Daniel Filipe
źródło
2

Zdecydowanie zgadzam się, że jest to idealne rozwiązanie dla metody przedłużania, ale myślę, że można to nieco poprawić.

public static string Remove(this string source, string remove,  int firstN)
    {
        if(firstN <= 0 || string.IsNullOrEmpty(source) || string.IsNullOrEmpty(remove))
        {
            return source;
        }
        int index = source.IndexOf(remove);
        return index < 0 ? source : source.Remove(index, remove.Length).Remove(remove, --firstN);
    }

Robi to trochę rekurencji, co zawsze jest zabawne.

Oto również prosty test jednostkowy:

   [TestMethod()]
    public void RemoveTwiceTest()
    {
        string source = "look up look up look it up";
        string remove = "look";
        int firstN = 2;
        string expected = " up  up look it up";
        string actual;
        actual = source.Remove(remove, firstN);
        Assert.AreEqual(expected, actual);

    }
Greg Roberts
źródło