Let us first think about and specifying the requirements, in this case using
Specification by Example, directly coded as executable unit tests:
[TestMethod]
public void Test_NameMatches()
{
Assert.IsTrue("John Paul Smith".Matches("Smith John"));
Assert.IsTrue("Smith John".Matches("Smith john"));
Assert.IsTrue("Smith Smith".Matches("Smith Smith"));
Assert.IsTrue("Smith Smith".Matches(" Smith Paul Smith "));
Assert.IsTrue("Smith John".Matches("john S"));
Assert.IsTrue("J Smith".Matches("John Smith" ));
Assert.IsTrue(" J Paul Smith ".Matches("John"));
Assert.IsTrue("Smith, J.P".Matches("John Paul Smith"));
Assert.IsTrue("John Smith".Matches("John Sm"));
Assert.IsFalse("John Jonsson".Matches("John Smith"));
Assert.IsFalse("John Smith".Matches( "John Jonsson"));
}
Notice here that I have included case with
duplicate names (3 and 4),
abbreviations (6-8) ,
case insensitivity (2 and 5),
extra white spaces (4, 7) and
punctuations (8). I also chose to match
incomplete names such as case 9 to support
auto-completion scenarios. For code readability I chose to go for a
String
extension method.
A concise solution that fulfills the above requirements is
static class Names
{
public static bool Matches(this String name1, String name2)
{
var names1 = name1.Split(Separators, StringSplitOptions.RemoveEmptyEntries);
var names2 = name2.Split(Separators, StringSplitOptions.RemoveEmptyEntries);
return names1.Length < names2.Length ? !names1.Except(names2, Comparer).Any() : !names2.Except(names1, Comparer).Any();
}
public static char[] Separators = { ' ', '\t', '.', ',' };
}
For comparison it utilizes
Comparer
which is defined as an instance of a nested class:
private static readonly NameComparer Comparer = new NameComparer();
private class NameComparer : IEqualityComparer<String>
{
public bool Equals(string x, string y)
{
return String.Compare(x, 0, y, 0, Math.Min(x.Length, y.Length), ignoreCase: true) == 0;
return String.Compare(x, 0, y, 0, x.Length, ignoreCase:true) == 0 ||
String.Compare(x, 0, y, 0, y.Length, ignoreCase:true) == 0; }
public int GetHashCode(string obj)
{
return Char.ToUpper(obj[0]).GetHashCode();
}
}
To support matching of a single name I also added the following method which takes a whole collection as input and returns all matches:
public static IEnumerable<String> GetAllMatches(this String name1, IEnumerable<String> dictionary)
{
var names1 = name1.Split(Separators, StringSplitOptions.RemoveEmptyEntries);
foreach (var name2 in dictionary)
{
var names2 = name2.Split(Separators, StringSplitOptions.RemoveEmptyEntries);
if (names1.Length < names2.Length ? !names1.Except(names2, Comparer).Any() : !names2.Except(names1, Comparer).Any())
{
yield return name2;
}
}
Does it passes the
peer review?