Удалить подстроку из списка строк

У меня есть список строк, содержащих запрещенные слова. Какой эффективный способ проверить, содержит ли строка какие-либо запрещенные слова, и удалить их из строки? На данный момент у меня так:

cleaned = String.Join(" ", str.Split().Where(b => !bannedWords.Contains(b,
                            StringComparer.OrdinalIgnoreCase)).ToArray());

Это отлично работает для отдельных запрещенных слов, но не для фраз (например, more than one word). Любой экземпляр more than one word также должен быть удален. Альтернативой, которую я хотел попробовать, является использование метода List из списка, но он возвращает только логическое значение, а не индекс соответствующего слова. Если бы я мог получить индекс подходящего слова, я мог бы просто использовать String.Replace(bannedWords[i],"");


person Skoder    schedule 07.04.2012    source источник
comment
В чем эффективность? Представление? Длина кода?   -  person zmbq    schedule 08.04.2012
comment
Проблема сводится к простому поиску и замене, пока вы не сделаете ключевую ошибку: thedailywtf.com/Articles/The-Clbuttic-Mistake-.aspx   -  person Kendall Frey    schedule 08.04.2012
comment
что не так с вашим решением в отношении «более одного слова» - если оно перекрывается, вы можете отсортировать их по длине, от большего к меньшему? Более эффективным было бы «перечислить» до конца, но вам все равно нужно быть осторожным с «перекрытиями».   -  person NSGaga-mostly-inactive    schedule 08.04.2012


Ответы (4)


Простой String.Replace не будет работать, так как он удалит части слова. Если «секс» — запрещенное слово, а у вас есть слово «секстет», которое не запрещено, вы должны оставить его как есть.

С помощью Regex вы можете найти целые слова и фразы в тексте с

string text = "A sextet is a musical composition for six instruments or voices.".
string word = "sex";
var matches = Regex.Matches(text, @"(?<=\b)" + word + @"(?=\b)");

В этом случае коллекция спичек будет пустой.

Вы можете использовать метод Regex.Replace

foreach (string word in bannedWords) {
    text = Regex.Replace(text, @"(?<=\b)" + word + @"(?=\b)", "")
}

Примечание. Я использовал следующий шаблон Regex

(?<=prefix)find(?=suffix)

где «префикс» и «суффикс» равны \b, что обозначает начало и конец слова.

Если ваши запрещенные слова или фразы могут содержать специальные символы, было бы безопаснее экранировать их с помощью Regex.Escape(word).


Используя идею @zmbq, вы можете создать шаблон Regex один раз с помощью

string pattern =
    @"(?<=\b)(" +
    String.Join(
        "|",
        bannedWords
            .Select(w => Regex.Escape(w))
            .ToArray()) +
     @")(?=\b)";
var regex = new Regex(pattern); // Is compiled by default

а затем многократно применять его к разным текстам с помощью

string result = regex.Replace(text, "");
person Olivier Jacot-Descombes    schedule 07.04.2012
comment
Спасибо, я думаю, что это лучший метод, но я заметил, что не сопоставляются целые слова. У меня есть x y zs, и я ищу x y z (совпадение не должно быть найдено, так как это x y zs), но совпадение найдено, и оно удаляет x y z, но оставляет s. Может быть, это ошибка с моей стороны, но она должна соответствовать всей фразе. - person Skoder; 08.04.2012
comment
Вы заметили, что я добавил скобки в @"(?<=\b)(" и @")(?=\b)"? В результате шаблон должен быть (?<=\b)(word1|word2|word3)(?=\b) - person Olivier Jacot-Descombes; 08.04.2012

Это не работает, потому что у вас противоречивые определения.

Когда вы хотите искать подпредложения, такие как more than one word, вы больше не можете разбивать по пробелам. Вам придется вернуться к String.IndexOf()

person Henk Holterman    schedule 07.04.2012

Если вам нужна производительность, я предполагаю, что вы беспокоитесь не о единовременной настройке, а о непрерывной производительности. Поэтому я бы создал одно огромное регулярное выражение, содержащее все запрещенные выражения, и убедился, что оно скомпилировано — это как установка.

Затем я попытался бы сопоставить его с текстом и заменить каждое совпадение пробелом или чем-то, чем вы хотите его заменить.

Причина этого в том, что большое регулярное выражение должно компилироваться во что-то, сравнимое с конечным автоматом, который вы создали бы вручную для решения этой проблемы, поэтому оно должно работать довольно хорошо.

person zmbq    schedule 07.04.2012

Почему бы вам не просмотреть список запрещенных слов и не найти каждое из них в строке с помощью метода string.IndexOf. Например, вы можете удалить запрещенные слова и фразы с помощью следующего фрагмента кода:

myForbWords.ForEach(delegate(string item) {
    int occ = str.IndexOf(item);
    if(occ > -1) str = str.Remove(occ, item.Length);
});

Тип myForbWords List<string>.

person Zafer    schedule 07.04.2012
comment
Кажется, это соответствует частичным словам, а не целым словам. Поэтому, если я попытаюсь заменить turn, это изменит turning на ing, но это не должно повлиять на turning, так как это не то слово, которое я хотел заменить. - person Skoder; 08.04.2012
comment
Вот так. Я пропустил это. Как вы указали, это может случиться. Таким образом, проверка следующего символа, который идет сразу после occ (появление), является ли он буквенно-цифровым и числовым символом или нет, устраняет эту проблему. - person Zafer; 08.04.2012