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.