C # - maneira mais simples de remover a primeira ocorrência de uma substring de outra string

87

Preciso remover a primeira (e SÓ a primeira) ocorrência de uma string de outra string.

Aqui está um exemplo de substituição da string "\\Iteration". Isto:

ProjectName \\ Iteration \\ Release1 \\ Iteration1

se tornaria isso:

ProjectName \\ Release1 \\ Iteration1

Aqui está um código que faz isso:

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;

Isso parece muito código.

Portanto, minha pergunta é a seguinte: existe uma maneira mais limpa / legível / mais concisa de fazer isso?

Vaccano
fonte

Respostas:

152
int index = sourceString.IndexOf(removeString);
string cleanPath = (index < 0)
    ? sourceString
    : sourceString.Remove(index, removeString.Length);
LukeH
fonte
10
Esta resposta pode falhar para strings que envolvam caracteres não ASCII. Por exemplo, na cultura en-US, æe aesão considerados iguais. A tentativa de remover paediade Encyclopædialançará um ArgumentOutOfRangeException, já que você está tentando remover 6 caracteres quando a substring correspondente contém apenas 5.
Douglas
6
Podemos modificá-lo assim: sourceString.IndexOf(removeString, StringComparison.Ordinal)para evitar a exceção.
Borislav Ivanov
29
string myString = sourceString.Remove(sourceString.IndexOf(removeString),removeString.Length);

EDITAR: @OregonGhost está certo. Eu mesmo quebraria o script com condicionais para verificar essa ocorrência, mas estava operando sob a suposição de que as cadeias de caracteres pertencessem umas às outras por algum requisito. É possível que as regras de tratamento de exceções exigidas pelos negócios capturem essa possibilidade. Eu mesmo usaria algumas linhas extras para realizar verificações condicionais e também para torná-lo um pouco mais legível para desenvolvedores juniores que podem não ter tempo para lê-lo completamente o suficiente.

Joel Etherton
fonte
9
Isso irá falhar se removeString não estiver contido em sourceString.
OregonGhost
26
sourceString.Replace(removeString, "");
malcolm waldron
fonte
18
String.Replace diz que " [r] eturns uma nova string na qual todas as ocorrências de uma string especificada na instância atual são substituídas por outra string especificada ". O OP queria substituir a primeira ocorrência.
Wai Ha Lee
6
Além disso, você deve explicar sua resposta um pouco, pois respostas apenas de código não são aceitáveis. Dê uma olhada nas outras respostas e compare-as com as suas para obter algumas dicas.
Wai Ha Lee
11

Escreveu um teste rápido TDD para isso

    [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 (entrada, "", 1); diz para procurar na entrada por qualquer coisa que corresponda ao padrão, com "", 1 vez.

CaffGeek
fonte
2
Assim você resolveu o problema. Considere o desempenho ao usar regex para um problema como este.
Thomas,
7

Você pode usar um método de extensão para se divertir. Normalmente eu não recomendo anexar métodos de extensão a uma classe de uso geral como string, mas como eu disse, isso é divertido. Peguei emprestada a resposta de @Lucke, pois não adianta reinventar a roda.

[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
fonte
3
Por que você normalmente não recomenda anexar métodos de extensão a uma classe de uso geral como String? Que aparentes desvantagens existem nisso?
Teun Kooijman
1
É fácil construir um método de extensão para um propósito muito específico para tê-lo nessa classe de propósito geral. Por exemplo, IsValidIBAN(this string input)seria muito específico para tê-lo na string.
Squirrelkiller
3

Se você quiser um método simples para resolver este problema. (Pode ser usado como uma extensão)

Ver abaixo:

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

Uso:

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

Inspirado e modificado pelas respostas de @ LukeH e @Mike.

Não se esqueça do StringComparison.Ordinal para evitar problemas com as configurações de cultura. https://www.jetbrains.com/help/resharper/2018.2/StringIndexOfIsCultureSpecific.1.html

Daniel Filipe
fonte
2

Eu definitivamente concordo que isso é perfeito para um método de extensão, mas acho que pode ser melhorado um pouco.

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

Isso causa um pouco de recursão, o que é sempre divertido.

Aqui está um teste de unidade simples:

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