Way to have String.Replace only hit "whole words"

C#.Netvb.netString

C# Problem Overview


I need a way to have this:

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

return this:

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

Basically I want to replace whole words, but not partial matches.

NOTE: I am going to have to use VB for this (SSRS 2008 code), but C# is my normal language, so responses in either are fine.

C# Solutions


Solution 1 - C#

A regex is the easiest approach:

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

The important part of the pattern is the \b metacharacter, which matches on word boundaries. If you need it to be case-insensitive use RegexOptions.IgnoreCase:

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

Solution 2 - C#

I've created a function (see blog post here) that wraps regex expression, suggested by Ahmad Mageed

/// <summary>
/// Uses regex '\b' as suggested in https://stackoverflow.com/questions/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;
}

Solution 3 - C#

As commented by Sga, the regex solution isn't perfect. And I guess not performance friendly too.

Here is my contribution :

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

... With test cases :

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

Solution 4 - C#

I just want to add a note about this particular regex pattern (used both in the accepted answer and in ReplaceWholeWord function). It doesn't work if what you are trying to replace isn't a word.

Here a test case:

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

(ready to try code: http://ideone.com/2Nt0A)

This has to be taken into consideration especially if you are doing batch translations (like I did for some i18n work).

Solution 5 - C#

If you want to define what characters make up a word i.e. "_" and "@"

you could use my (vb.net) function:

 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

Test

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

Result

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

Solution 6 - C#

I don't like Regex because it is slow. My function is faster.

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

Solution 7 - C#

This method also ignores the case if you are interested

public static string Replace(this string s, string word, string by, StringComparison stringComparison, bool WholeWord)
{
    s = s + " ";
    int wordSt;
    StringBuilder sb = new StringBuilder();
    while (s.IndexOf(word, stringComparison) > -1)
    {
        wordSt = s.IndexOf(word, stringComparison);
        if (!WholeWord || ((wordSt == 0 || !Char.IsLetterOrDigit(char.Parse(s.Substring(wordSt - 1, 1)))) && !Char.IsLetterOrDigit(char.Parse(s.Substring(wordSt + word.Length, 1)))))
        {
            sb.Append(s.Substring(0, wordSt) + by);
        }
        else
        {
            sb.Append(s.Substring(0, wordSt + word.Length));
        }
        s = s.Substring(wordSt + word.Length);
    }
    sb.Append(s);
    return sb.ToString().Substring(0, sb.Length - 1);
}

Solution 8 - C#

You could use the string.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();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVaccanoView Question on Stackoverflow
Solution 1 - C#Ahmad MageedView Answer on Stackoverflow
Solution 2 - C#Michael FreidgeimView Answer on Stackoverflow
Solution 3 - C#Alexis PautrotView Answer on Stackoverflow
Solution 4 - C#SgaView Answer on Stackoverflow
Solution 5 - C#Frank_VrView Answer on Stackoverflow
Solution 6 - C#palotaView Answer on Stackoverflow
Solution 7 - C#Honey22SharpView Answer on Stackoverflow
Solution 8 - C#AlexView Answer on Stackoverflow