A lookup table for predicates?

I have a C# application with a user interface that contains options for the type of search a user can perform. The options are 'multiple terms' (splits the search term on spaces), 'case sensitive', and 'regular expression'. More options may be added in the future.

The options are stored in the properties IsMultipleTerms, IsCaseSensitive, and IsRegularExpression.

Each combination of options has a different search predicate, and search predicates are defined like so:

private bool SearchCaseInsensitive(string field)
{
    return field.ToLower().Contains(_searchTermLower);
}

private bool SearchCaseInsensitiveMultiple(string field)
{
    return _searchTermsLower.All(field.ToLower().Contains);
}

I filter the list like so:

var predicate = GetFilterPredicate();

SearchResults.Where(predicate);

I currently achieve the lookup by using a class called SearchPredicateOptionSet:

public class PredicateOptionSet
{
    public bool IsCaseSensitive { get; set; }
    public bool IsRegularExpression { get; set; }
    public bool IsMultipleTerms { get; set; }

    public Func<SearchResult, bool> Predicate { get; set; }

    public PredicateOptionSet(bool isCaseSensitive, bool isRegularExpression, bool isMultipleTerms, 
        Func<SearchResult, bool> predicate)
    {
        IsCaseSensitive = isCaseSensitive;
        IsRegularExpression = isRegularExpression;
        IsMultipleTerms = isMultipleTerms;

        Predicate = predicate;
    }
}

I create a list of them and then query it:

private readonly List<PredicateOptionSet> _predicates;

public MainWindow()
{
    _predicates = new List<PredicateOptionSet>
    {
        new PredicateOptionSet(true, false, false, result => Search(result.Name)),
        new PredicateOptionSet(false, false, false, result => SearchCaseInsensitive(result.Name)),

        new PredicateOptionSet(true, false, true, result => SearchMultiple(result.Name)),
        new PredicateOptionSet(false, false, true, result => SearchCaseInsensitiveMultiple(result.Name)),
    };
}

private Func<SearchResult, bool> GetFilterPredicate()
{
    var predicate = from p in _predicates
        where p.IsCaseSensitive == IsCaseSensitive &&
            p.IsMultipleTerms == IsMultipleTerms &&
            p.IsRegularExpression == IsRegularExpression
        select p.Predicate;

    return predicate.First();
}

Is there a cleaner way to achieve this? I feel like I may be missing an important concept.

Answers


At least for the check part you could use an Enum with the [Flags] attribute to create bit field. That might be a little more extensible if you add more methods in the future. You could then use a simple lookup table and do away with the PredicateOptionSet class. Example:

[Flags]
public enum PredicateOption
{
    IsCaseSensitive, IsRegularExpression, IsMultipleTerms
};

...

public Dictionary<PredicateOption, Func<SearchResult, bool>> _predicates
    = new Dictionary<PredicateOption, Func<SearchResult, bool>>();
_predicates.Add(PredicateOption.IsCaseSensitive, result => Search(result.Name));
_predicates.Add(PredicateOption.IsCaseSensitive | PredicateOption.IsMultipleTerms,
    result => SearchCaseInsensitiveMultiple(result.Name));

....

PredicateOption option = PredicateOption.IsCaseSensitive | PredicateOption.IsMultipleTerms;
SearchResults.Where(_predicates[option]);

Maybe I see it wrong, but currently you have 2 fundamentally different search strategies: the normal and the regex. Both strategies support an option to be either case sensitive or not, but this could be a parameter of the strategy. The multi match problem is already somewhat special, because you anyway have to split the search term first and then you could already delegate back to one of the simple search strategies (combining the search either with AND or with OR).

Creating a separate Func implementation for each combination of these aspects feels a bit of "overkill". If there will be some more options in the future it is indeed tempting to find a generalized approach that handles these options "equal", but on the other hand these options behave quite differently. It is also on the con side for future extensions that you run into the combinatorial explosion of different implementations.


It seems that you could make one function per option, then chain them (through Where(...) calls). That way, you only have three methods instead of Four. Also, you can then combine the smaller operation in more ways if you ever need to.


Need Your Help

java equivalent to .NET MVC4 + Entity Framework (I mean options like Grails, Spring MVC..etc.. not sure)

c# java asp.net-mvc

This is a simple and very specific question: If you have been doing a project with .NET MVC4 and Entity Framework with lots of jQuery/Json using, of couse, Visual Studio 2012, and you are asked to

Why is my python script that runs the adb shell monkey command crashing for large values of events?

android python subprocess adb

I have written a small python function that runs an adb shell monkey -p -v command along with an adb logcat command using subprocess.popen. For values larger than 100, this program crashes and I'...

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.