C # - Le moyen le plus simple de supprimer la première occurrence d'une sous-chaîne d'une autre chaîne

87

Je dois supprimer la première (et UNIQUEMENT la première) occurrence d'une chaîne d'une autre chaîne.

Voici un exemple de remplacement de la chaîne "\\Iteration". Ce:

Nom du projet \\ Iteration \\ Release1 \\ Iteration1

deviendrait ceci:

Nom du projet \\ Release1 \\ Iteration1

Voici un code qui fait cela:

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;

Cela semble beaucoup de code.

Ma question est donc la suivante: existe-t-il un moyen plus propre / plus lisible / plus concis de le faire?

Vaccano
la source

Réponses:

152
int index = sourceString.IndexOf(removeString);
string cleanPath = (index < 0)
    ? sourceString
    : sourceString.Remove(index, removeString.Length);
LukeH
la source
10
Cette réponse peut être interrompue pour les chaînes comportant des caractères non ASCII. Par exemple, dans la culture en-US, æet aesont considérés comme égaux. Tenter de supprimer paediade Encyclopædialèvera un ArgumentOutOfRangeException, puisque vous essayez de supprimer 6 caractères alors que la sous-chaîne correspondante ne contient que 5.
Douglas
6
Nous pouvons le modifier comme ceci: sourceString.IndexOf(removeString, StringComparison.Ordinal)pour éviter l'exception.
Borislav Ivanov
29
string myString = sourceString.Remove(sourceString.IndexOf(removeString),removeString.Length);

EDIT: @OregonGhost a raison. Je briserais moi-même le script avec des conditions pour vérifier une telle occurrence, mais j'opérais en supposant que les chaînes étaient données pour appartenir les unes aux autres par une exigence. Il est possible que les règles de gestion des exceptions requises par l'entreprise attrapent cette possibilité. J'utiliserais moi-même quelques lignes supplémentaires pour effectuer des vérifications conditionnelles et aussi pour le rendre un peu plus lisible pour les développeurs juniors qui pourraient ne pas prendre le temps de le lire suffisamment complètement.

Joël Etherton
la source
9
Cela plantera si removeString n'est pas contenu dans sourceString.
OregonGhost
26
sourceString.Replace(removeString, "");
Malcolm Waldron
la source
18
String.Replace dit qu'il " [r] et renvoie une nouvelle chaîne dans laquelle toutes les occurrences d'une chaîne spécifiée dans l'instance actuelle sont remplacées par une autre chaîne spécifiée ". Le PO voulait remplacer la première occurrence.
Wai Ha Lee
6
De plus, vous devriez expliquer un peu votre réponse car les réponses de code uniquement ne sont pas acceptables. Jetez un œil aux autres réponses et comparez-les aux vôtres pour obtenir des conseils.
Wai Ha Lee
11

A écrit un test TDD rapide pour cela

    [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 (entrée, "", 1); dit de chercher en entrée tout ce qui correspond au modèle, avec "", 1 fois.

CaffGeek
la source
2
Comme ça, vous avez résolu le problème. Tenez simplement compte des performances lors de l'utilisation de regex pour un problème comme celui-ci.
Thomas
7

Vous pouvez utiliser une méthode d'extension pour le plaisir. En général, je ne recommande pas d'attacher des méthodes d'extension à une classe à usage général comme string, mais comme je l'ai dit, c'est amusant. J'ai emprunté la réponse de @ Luke car il ne sert à rien de réinventer la roue.

[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
la source
3
Pourquoi ne recommandez-vous généralement pas d'attacher des méthodes d'extension à une classe à usage général telle que String? Quels inconvénients apparents y a-t-il à cela?
Teun Kooijman
1
Il est facile de créer une méthode d'extension dans un but trop spécifique pour l'avoir sur une telle classe à usage général. Par exemple, IsValidIBAN(this string input)serait trop spécifique pour l'avoir sur une chaîne.
Squirrelkiller
3

Si vous souhaitez une méthode simple pour résoudre ce problème. (Peut être utilisé comme extension)

Voir ci-dessous:

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

Usage:

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

Inspiré et modifié des réponses de @ LukeH et @ Mike.

N'oubliez pas StringComparison.Ordinal pour éviter les problèmes avec les paramètres de culture. https://www.jetbrains.com/help/resharper/2018.2/StringIndexOfIsCultureSpecific.1.html

Daniel Filipe
la source
2

Je suis tout à fait d'accord pour dire que c'est parfait pour une méthode d'extension, mais je pense que cela peut être amélioré un peu.

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

Cela fait un peu de récursivité, ce qui est toujours amusant.

Voici également un test unitaire simple:

   [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
la source