Thursday 19 February 2009

Practical LINQ: Generating the next default name

Pleasurable as it is, constructing LINQ queries to solve abstruse Mathematical problems, a developer’s real purpose in life, as his boss reminds him often enough, is creating business value. Happily I’ve found that that can be done using LINQ too.

Here’s an example that came up recently.

I was writing a Widget editor dialog box with functionality to create new Widgets. Being the helpful soul I am, I wanted to give the new widgets default names, “Widget1”, “Widget2” and so on (I can be helpful or imaginative, but not both!). But the names also needed to be unique: if “Widget1” and “Widget2” were already in the list, the next new widget should be called “Widget3”.

This is the method I came up with:

public static class NamingExtensions
{
    public static string GetNextDefaultName(this IEnumerable<string> existingNames, string defaultNamePrefix)
    {
        if (existingNames == null)
        {
            throw new ArgumentNullException("existingNames");
        }

        defaultNamePrefix = defaultNamePrefix ?? string.Empty;
        string matchNumberInDefaultNamePattern = "^" + defaultNamePrefix + @"(\d+)$";

        const RegexOptions regexOptions = RegexOptions.IgnoreCase | RegexOptions.Singleline;

        int nextNumber = existingNames.Select(name => Regex.Match(name, matchNumberInDefaultNamePattern, regexOptions) )
            .Where(match => match.Success)
            .Select(match => match.Groups[1].Value)
            .Select(matchValue => int.Parse(matchValue, CultureInfo.InvariantCulture))
            .Aggregate(0, Math.Max, max => max + 1);

        return defaultNamePrefix + nextNumber.ToString(CultureInfo.InvariantCulture);
    }
}

Since I wrote it as an extension method, I can use it like this:

var existingNames = new[] { "Widget1", "Widget2" };
var nextName = existingNames.GetNextDefaultName("Widget");

You might wonder why, in line 19, I use Aggregate instead of a straightforward Max()? That’s because Max() throws an exception if you try using it on an empty sequence, which might happen if none of the existing names match the “Widget[n]” pattern. Also, Aggregate has the nice overload that allows you to apply a result selector function to the final aggregate: as you see, I take advantage of this to return the next number in the sequence, rather than the max itself.

Isn’t LINQ lovely?

2 comments:

Anonymous said...

Dan liked this.
And he should really find a couple of hours one day to study Regular Expressions.

Unknown said...

Thanks Dan,
Regular expressions are certainly worth the steep climb up the learning curve!

Post a Comment