Спосіб мати рядок. Замінити лише натиснуті "цілі слова"


76

Мені потрібен спосіб отримати це:

"test, and test but not testing.  But yes to test".Replace("test", "text")

повернути це:

"text, and text but not testing.  But yes to text"

В основному я хочу замінити цілі слова, але не часткові збіги.

ПРИМІТКА: Мені доведеться використовувати VB для цього (код SSRS 2008), але C # є моєю звичайною мовою, тому відповіді в будь-якому з них чудові.


Це дублюється тут, я думаю: stackoverflow.com/questions/1209049/regex-match-whole-words
James Michael Hare

Я думаю, найпростішим способом (можливо, не найкращим способом) було б додати пробіл на початку та в кінці пошукового терміна, наприклад, замінити цілі слова, шукати: "drown", щоб він не замінив такі речі як "утоплення".
jay_t55

Відповіді:


126

Регулярний вираз - це найпростіший підхід:

string input = "test, and test but not testing.  But yes to test";
string pattern = @"\btest\b";
string replace = "text";
string result = Regex.Replace(input, pattern, replace);
Console.WriteLine(result);

Важливою частиною шаблону є \bметасимвол, який відповідає межам слова. Якщо вам потрібно, щоб воно не враховувало регістр RegexOptions.IgnoreCase:

Regex.Replace(input, pattern, replace, RegexOptions.IgnoreCase);

15
Де \bрегулярні вирази говорять про межі слів.
Одійдено

6
Ваше рішення чудове! Якщо я багато публікую обгортку fn із вимиканням регулярного виразу:static string ReplaceFullWords( string input, string from, string to) { if (input == null) { return null; } return Regex.Replace(input, "\\b" + Regex.Escape(from) + "\\b", to); }
Стефані

Рядок повинен бутиstring pattern = "\\btest\\b";
Валамас

24

Я створив функцію (див. Допис у блозі тут ), яка обгортає вираз регулярного виразу, запропонований Ахмадом Магідом

/// <summary>
/// Uses regex '\b' as suggested in /programming/6143642/way-to-have-string-replace-only-hit-whole-words
/// </summary>
/// <param name="original"></param>
/// <param name="wordToFind"></param>
/// <param name="replacement"></param>
/// <param name="regexOptions"></param>
/// <returns></returns>
static public string ReplaceWholeWord(this string original, string wordToFind, string replacement, RegexOptions regexOptions = RegexOptions.None)
{
    string pattern = String.Format(@"\b{0}\b", wordToFind);
    string ret=Regex.Replace(original, pattern, replacement, regexOptions);
    return ret;
}

5
Чи не забувайте використовувати Regex.Escape()на wordToFindтак спеціальні символи інтерпретуються як звичайні символи.
CheeseSucker 07.03.16

@MichaelFreidgeim, Regex.Escape () робить величезну різницю, якщо wordToFind більше, ніж буквено-цифровий. Наприклад, спробуйте знайти масковане слово ":% @ # \". Це просто не буде працювати, як очікувалося.
Jroonk

@Jroonk, ви можете відредагувати допис, якщо це покращить відповідь
Michael Freidgeim

8

Як прокоментував Sga, рішення регулярного виразу не є ідеальним. І, мабуть, це не дуже зручно.

Ось мій внесок:

public static class StringExtendsionsMethods
{
    public static String ReplaceWholeWord ( this String s, String word, String bywhat )
    {
        char firstLetter = word[0];
        StringBuilder sb = new StringBuilder();
        bool previousWasLetterOrDigit = false;
        int i = 0;
        while ( i < s.Length - word.Length + 1 )
        {
            bool wordFound = false;
            char c = s[i];
            if ( c == firstLetter )
                if ( ! previousWasLetterOrDigit )
                    if ( s.Substring ( i, word.Length ).Equals ( word ) )
                    {
                        wordFound = true;
                        bool wholeWordFound = true;
                        if ( s.Length > i + word.Length )
                        {
                            if ( Char.IsLetterOrDigit ( s[i+word.Length] ) )
                                wholeWordFound = false;
                        }

                        if ( wholeWordFound )
                            sb.Append ( bywhat );
                        else
                            sb.Append ( word );

                        i += word.Length;
                    }

            if ( ! wordFound )
            {
                previousWasLetterOrDigit = Char.IsLetterOrDigit ( c );
                sb.Append ( c );
                i++;
            }
        }

        if ( s.Length - i > 0 )
            sb.Append ( s.Substring ( i ) );

        return sb.ToString ();
    }
}

... З тестовими кейсами:

String a = "alpha is alpha";
Console.WriteLine ( a.ReplaceWholeWord ( "alpha", "alphonse" ) );
Console.WriteLine ( a.ReplaceWholeWord ( "alpha", "alf" ) );

a = "alphaisomega";
Console.WriteLine ( a.ReplaceWholeWord ( "alpha", "xxx" ) );

a = "aalpha is alphaa";
Console.WriteLine ( a.ReplaceWholeWord ( "alpha", "xxx" ) );

a = "alpha1/alpha2/alpha3";
Console.WriteLine ( a.ReplaceWholeWord ( "alpha", "xxx" ) );

a = "alpha/alpha/alpha";
Console.WriteLine ( a.ReplaceWholeWord ( "alpha", "alphonse" ) );

1
@Alexis, вам слід перейменувати функцію як ReplaceWhitespaceSeparatedSubstrings. Також, будь-ласка, надайте очікуваний вихідний "коментар для кожного з тестових випадків. Якщо ви зробили порівняння продуктивності з підходом регулярних
виразів

Просто запустіть тестові кейси, щоб побачити результати.
Alexis Pautrot

1
Це не "розділений пробілами", а "будь-який символ, не розділений літерою або цифрою". Ні, я не робив порівнянь.
Alexis Pautrot

2
Я працював із цим і виявив одну помилку: a = "4,99"; Console.WriteLine (a.ReplaceWholeWord ("9", "8,99")); результати в 4.98.99. У цьому контексті це виглядає безглуздим прикладом, але це ілюструє проблему, яку я маю щодо реального проекту.
Уолтер Вільямс,

6

Я просто хочу додати примітку щодо цього конкретного шаблону регулярного виразу (використовується як у прийнятій відповіді, так і у функції ReplaceWholeWord ). Це не працює, якщо те, що ви намагаєтесь замінити, не є словом .

Ось тестовий приклад:

using System;
using System.Text.RegularExpressions;
public class Test
{
    public static void Main()
    {
        string input = "doin' some replacement";
        string pattern = @"\bdoin'\b";
        string replace = "doing";
        string result = Regex.Replace(input, pattern, replace);
        Console.WriteLine(result);
    }
}

(готовий спробувати код: http://ideone.com/2Nt0A )

Це слід враховувати, особливо якщо ви робите пакетні переклади (як я робив для деяких робіт i18n).


Це очікується. "Doin '" - це не "ціле слово". Ви намагаєтесь замінити "підрядки, розділені пробілами"
Майкл Фрейдгайм,

1

Якщо ви хочете визначити, з яких символів складається слово, тобто "_" та "@"

Ви можете використовувати мою функцію (vb.net):

 Function Replace_Whole_Word(Input As String, Find As String, Replace As String)
      Dim Word_Chars As String = "ABCDEFGHIJKLMNOPQRSTUVWYXZabcdefghijklmnopqrstuvwyxz0123456789_@"
      Dim Word_Index As Integer = 0
      Do Until False
         Word_Index = Input.IndexOf(Find, Word_Index)
         If Word_Index < 0 Then Exit Do
         If Word_Index = 0 OrElse Word_Chars.Contains(Input(Word_Index - 1)) = False Then
            If Word_Index + Len(Find) = Input.Length OrElse Word_Chars.Contains(Input(Word_Index + Len(Find))) = False Then
               Input = Mid(Input, 1, Word_Index) & Replace & Mid(Input, Word_Index + Len(Find) + 1)
            End If
         End If
         Word_Index = Word_Index + 1
      Loop
      Return Input
   End Function

Тест

Replace_Whole_Word("We need to replace words tonight. Not to_day and not too well to", "to", "xxx")

Результат

"We need xxx replace words tonight. Not to_day and not too well xxx"

0

Я не люблю регулярний вираз, оскільки він повільний. Моя функція швидша.

public static string ReplaceWholeWord(this string text, string word, string bywhat)
{
    static bool IsWordChar(char c) => char.IsLetterOrDigit(c) || c == '_';
    StringBuilder sb = null;
    int p = 0, j = 0;
    while (j < text.Length && (j = text.IndexOf(word, j, StringComparison.Ordinal)) >= 0)
        if ((j == 0 || !IsWordChar(text[j - 1])) &&
            (j + word.Length == text.Length || !IsWordChar(text[j + word.Length])))
        {
            sb ??= new StringBuilder();
            sb.Append(text, p, j - p);
            sb.Append(bywhat);
            j += word.Length;
            p = j;
        }
        else j++;
    if (sb == null) return text;
    sb.Append(text, p, text.Length - p);
    return sb.ToString();
}

-2

Ви можете використовувати рядок.replace

string input = "test, and test but not testing.  But yes to test";
string result2 = input.Replace("test", "text");
Console.WriteLine(input);
Console.WriteLine(result2);
Console.ReadLine();

7
Я не фахівець на C #, але як replaceне зміниться testingна те texting, що задається у питанні?
Король Мідас,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.