C # - Самый простой способ удалить первое вхождение подстроки из другой строки

87

Мне нужно удалить первое (и ТОЛЬКО первое) вхождение строки из другой строки.

Вот пример замены строки "\\Iteration". Этот:

ProjectName \ Iteration \ Release1 \ Iteration1

станет таким:

ProjectName \ Release1 \ Iteration1

Вот код, который делает это:

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;

Похоже, много кода.

Итак, мой вопрос таков: есть ли более чистый / более читаемый / более сжатый способ сделать это?

Vaccano
источник

Ответы:

152
int index = sourceString.IndexOf(removeString);
string cleanPath = (index < 0)
    ? sourceString
    : sourceString.Remove(index, removeString.Length);
Лука
источник
10
Этот ответ может нарушиться для строк, содержащих символы, отличные от ASCII. Например, в культуре en-US æи aeсчитаются равными. Попытка удалить paediaиз Encyclopædiaприведет к сбою ArgumentOutOfRangeException, поскольку вы пытаетесь удалить 6 символов, когда соответствующая подстрока содержит только 5.
Дуглас
6
Мы можем изменить его так: sourceString.IndexOf(removeString, StringComparison.Ordinal)чтобы избежать исключения.
Борислав Иванов
29
string myString = sourceString.Remove(sourceString.IndexOf(removeString),removeString.Length);

РЕДАКТИРОВАТЬ: @OregonGhost прав. Я сам разбивал сценарий на условные обозначения, чтобы проверить наличие такого случая, но я работал в предположении, что строки были даны как принадлежащие друг другу по какому-то требованию. Возможно, ожидается, что бизнес-правила обработки исключений уловят эту возможность. Я сам использовал бы пару дополнительных строк для выполнения условных проверок, а также чтобы сделать его более читабельным для младших разработчиков, которым, возможно, не хватает времени, чтобы прочитать его достаточно внимательно.

Джоэл Этертон
источник
9
Это приведет к сбою, если removeString не содержится в sourceString.
OregonGhost,
26
sourceString.Replace(removeString, "");
Малькольм Уолдрон
источник
18
String.Replace сообщает, что он « [r] возвращает новую строку, в которой все вхождения указанной строки в текущем экземпляре заменяются другой указанной строкой ». ОП хотел заменить первое вхождение.
Вай Ха Ли
6
Кроме того, вам следует немного пояснить свой ответ, поскольку ответы, содержащие только код, неприемлемы. Взгляните на другие ответы и сравните их со своим, чтобы получить несколько советов.
Вай Ха Ли
11

Написал для этого быстрый тест 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 (ввод, "", 1); говорит искать во вводе все, что соответствует шаблону, с "", 1 раз.

CaffGeek
источник
2
Так ты решил проблему. Просто подумайте о производительности при использовании регулярного выражения для такой проблемы.
Thomas
7

Вы можете использовать метод расширения для развлечения. Обычно я не рекомендую прикреплять методы расширения к такому классу общего назначения, как строка, но, как я уже сказал, это весело. Я позаимствовал ответ @Luke, так как нет смысла изобретать колесо заново.

[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);
    }
}
Майк Валенти
источник
3
Почему вы обычно не рекомендуете прикреплять методы расширения к такому классу общего назначения, как String? Какие у этого очевидные недостатки?
Теун
1
Легко создать метод расширения для слишком конкретной цели, чтобы иметь его в таком классе общего назначения. Например, IsValidIBAN(this string input)было бы слишком конкретно, чтобы иметь его в строке.
Squirrelkiller
3

Если вам нужен простой способ решения этой проблемы. (Может использоваться как расширение)

Увидеть ниже:

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

Применение:

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

Вдохновленный и модифицированный ответами @LukeH и @Mike.

Не забудьте StringComparison.Ordinal, чтобы избежать проблем с настройками культуры. https://www.jetbrains.com/help/resharper/2018.2/StringIndexOfIsCultureSpecific.1.html

Даниэль Филипе
источник
2

Я определенно согласен с тем, что это идеально подходит для метода расширения, но я думаю, что его можно немного улучшить.

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

Это немного рекурсии, что всегда весело.

Вот и простой модульный тест:

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

    }
Грег Робертс
источник