C # - найпростіший спосіб видалити перше входження підрядка з іншого рядка


87

Мені потрібно видалити перше (і ТІЛЬКИ перше) входження рядка з іншого рядка.

Ось приклад заміни рядка "\\Iteration". Це:

Ім'я проекту \\ Ітерація \\ Випуск1 \\ Ітерація1

стане таким:

Ім'я проекту \\ Випуск1 \\ Ітерація1

Ось код, який робить це:

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;

Це здається багато коду.

Отже, моє запитання таке: чи є для цього більш чистий / читабельний / стислий спосіб?

Відповіді:


152
int index = sourceString.IndexOf(removeString);
string cleanPath = (index < 0)
    ? sourceString
    : sourceString.Remove(index, removeString.Length);

10
Ця відповідь може порушитись для рядків із символами, що не належать до ASCII. Наприклад, в рамках культури США æі aeвважаються рівними. Спроба вилучити paediaз Encyclopædiaвикине ArgumentOutOfRangeException, оскільки ви намагаєтеся видалити 6 символів, коли відповідна підрядок містить лише 5.
Дуглас

6
Ми можемо змінити його так: sourceString.IndexOf(removeString, StringComparison.Ordinal)щоб уникнути винятку.
Борислав Іванов

29
string myString = sourceString.Remove(sourceString.IndexOf(removeString),removeString.Length);

EDIT: @OregonGhost має рацію. Я сам розбивав би скрипт з умовними умовами, щоб перевірити подібні випадки, але я працював, припускаючи, що рядки отримали належність один одному за якоюсь вимогою. Можливо, передбачається, що бізнес вимагає правил обробки винятків, щоб передбачити цю можливість. Я сам би використав пару додаткових рядків для виконання умовних перевірок, а також для того, щоб зробити його трохи читабельнішим для молодших розробників, які, можливо, не знайдуть часу, щоб прочитати його досить ретельно.


9
Це призведе до аварії, якщо removeString не міститься у sourceString.
OregonGhost

26
sourceString.Replace(removeString, "");

18
String.Replace говорить, що він " [r] видає новий рядок, у якому всі випадки вказаного рядка в поточному екземплярі замінюються на інший вказаний рядок ". OP хотів замінити перший випадок.
Wai Ha Lee

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 раз.


2
Як ви вирішили проблему. Просто враховуйте продуктивність, використовуючи регулярний вираз для такої проблеми.
Томас,

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? Які явні мінуси у цього?
Teun Kooijman

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's та @ 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);

    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.