Simple string keyword search using ExtensionMethods and LINQ

This is a case I ran into recently. I need to create a multiple OR condition with LINQ. I have a list of user names and I wish to create a simple search where I can match any first name/middle name/last name combination with any of the names in my list. The search “Brian Pedersen” should match any combination such as ”Brian Jensen”, “Brian Steen Pedersen”, “Johan Pedersen” and “Brian Pedersen”.

This can be achieved very simply using LINQ. I need to split my search term into single words and use the Any LINQ statement:

// Remember to include LINQ
using System.Linq;

// This is the string to search within
string userName = "Brian Steen Pedersen";
// This is the search string
string searchString = "Brian Pedersen";
// I split the search string into indiviual words
string[] searchTerms = seachString.Split(' ');
// ... and return TRUE if any of the words is found inside the userName string
bool found = searchTerms.Any(searchTerm => userName.ToLower().Contains(searchTerm.ToLower()));

All I have to do is to put the code into a function and I can call this function whenever I need it:

public bool ContainsAny(string userName, IEnumerable<string> searchTerms)
{
  return searchTerms.Any(searchTerm => userName.ToLower().Contains(searchTerm.ToLower()));
}

Now, wouldn’t it be nice if the “ContainsAny” was a function of the String object? The String object Contains function only returns true for any exact match, and that’s not what I want. I want my ContainsAny function.

The String object can be extended using an Extension Method. Extension methods enable you to add methods to existing types without creating or modifying the original type. Read more about extension methods here.

This is 2 extension methods, extending the String object with ContainsAny and ContainsAll:

public static class StringExtension
{
  public static bool ContainsAny(this string str, IEnumerable<string> searchTerms)
  {
    return searchTerms.Any(searchTerm => str.ToLower().Contains(searchTerm.ToLower()));
  }  

  public static bool ContainsAll(this string str, IEnumerable<string> searchTerms)
  {
    return searchTerms.All(searchTerm => str.ToLower().Contains(searchTerm.ToLower()));
  }
}

With this code I can now use ContainsAny directly on the String object:

string userName = "Brian Steen Pedersen";
string searchString = "Brian Pedersen";

bool found = userName.ContainsAny(searchString.Split(' '));

I can even combine it with other LINQ statements. Lets assume that my users is stored in a generic List<string>. I can now find all users with this line of code:

// This is my fictive list of all users
List<string> allUsers = UserRepository.GetAllusers();
// This is my search string
string searchString = "Brian Pedersen";
// Here is all my users that match any words in the search string
List<string> foundUsers = allUsers.FindAll(s => s.ContainsAny(searchString.Split(' ')));

Generic lists and Predicates

Recently I was working with a generic list containing my own Favorite class. My Favorite class contains an url and a title:

using System.Collections.Generic;

public class Favorites
{
  private List<Favorite> _favoriteList = new List<Favorite>();
}

One of the functions on my list is to remove all Favorites with a specific url. I quickly jumped to the help file and found the RemoveAll() function which takes a Predicate. A predicate is a generic delegate that takes the same type as the List<> and returns true if the parameter matches a statement:

public void DinosaurExample()
{
  List<string> dinosaurs = new List<string>();

  dinosaurs.Add("Compsognathus");
  dinosaurs.Add("Amargasaurus");
  dinosaurs.Add("Oviraptor");
  dinosaurs.RemoveAll(EndsWithSaurus));
}

private static bool EndsWithSaurus(String s)
{
  if ((s.Length > 5) &&
     (s.Substring(s.Length - 6).ToLower() == "saurus"))
  {
    return true;
  }
  else
  {
    return false;
  }
}

 The EndsWithSaurus(String s) is the predicate and the function returns true if the matching string ends with “saurus”, removing “Amargasaurus” from the list.

But hey! What if I don’t have a hard-coded match in my code (I rarely have)? The predicate cannot take any parameters. Also, it’s a Static member, polluting my class.

The solution is to create a “predicate class” that takes the expression to match as a parameter:

internal class FavoriteUrlMatch
{
  private string _url;
 
  public FavoriteUrlMatch(string url)
  {
    _url = url;
  }
   
  public bool Match(Favorite fav)
  {
    return fav.Url == _url;
  }
}

Then I can invoke my FavoriteUrlMatch class from my Favorites class:

public void Delete(string url)
{
  FavoriteUrlMatch match = new FavoriteUrlMatch(url);
  _favoriteList.RemoveAll(match.Match);
}

I am not very proud of this solution, but I cannot find a better one.

Follow

Get every new post delivered to your Inbox.

Join 92 other followers