1

I have a database full of ModelNumbers that I want a user to be able to search for. The tricky part is that some of the model numbers have lots of funny characters including:

* - Alphanumeric wildcard
# - Numeric wildcard
(AB,CD) - List of options

So for example model number 1234#*(AB,CD)

The following searches should return this model number:

  1. 12341ACD - full part number with options
  2. 12341A - "starts with" with some wildcards filled in.
  3. 2341 - "contains" with some wildcards filled in.

I have attempted to use regular expressions. I have written some code to convert these wild card characters into regular expressions; that code was relatively easy and I have good unit tests for it. A regular expression of 1234[0-9]?[A-Z0-9]?(AB|CD)? will work for the first search string; the regex will not for #2 and #3 since it only matches a part of the pattern.

I tried using PCRE with partial matching, but the intent for that is for evaluating the regex as a user types (from left to right), so the partial match only works for #1 and #2.

I do not want to use fuzzy matching. I am trying to return only specific matching model numbers. The results of the search will represent "qualified" model numbers, so I cannot return model numbers that are only "close" to the search text.

How can I do this? I'm not looking for help with the code, but maybe instead for a strategy. Is there some way to use a regex with only a "contains" type of match? Should I use regex at all for this?

12
  • 1
    Thank you for the upvotes. I think this is a very interesting problem. I have some ideas, but I have already burned days on the regular expressions and had to abort that. If I could do a "contains" match of a regular expression I think that would work, but as I say, PCRE only supports "starts with" partial matches.
    – Jess
    Commented Sep 16, 2021 at 14:28
  • 2
    You've got a two-way pattern matching requirement (both the model numbers and the search strings contain wildcards), and this probably can't be handled well using regular expressions. Commented Sep 16, 2021 at 14:52
  • 1
    The implicit wildcard is the problem. If you didn't have that, you'd just convert your model numbers to regular expressions and evaluate them sequentially. Converting the regular expressions into a form that also matches substrings is much harder and probably not very efficient. Commented Sep 16, 2021 at 15:26
  • 5
    My gut feel is that this is a hard problem, to the level of "if it's not absolutely essential for your business, push back on the requirement". If you can phrase this in the right terms (i.e. your "model number" is a regular language), then you might get good answers as to whether it is even possibly feasible over on Computer Science. Commented Sep 16, 2021 at 15:27
  • 1
    It sounds like the core issue is a violation of 1st Normal Form - i.e. a single field which appears to contain many separate queryable data items encoded within it. If you have any control over the underlying database then I would suggest looking to normalising the model numbers into separate, indexable fields. Otherwise you might want to consider a separate search/query database (could be relational or a NOSQL solution) for these model numbers where you have full control and would be able to move away from the need to match patterns. Commented Sep 17, 2021 at 7:11

1 Answer 1

1

I created an iterator which would generate literal model numbers from the wildcard pattern. This worked but the technique ended up generating way too many model numbers. A numeric wildcard is not so bad, but the number of permutations for an alpha-numeric wildcard adds up VERY fast; just 4 wildcards would yield more than a million permutations.

    /// <summary>
    /// Get a distinct list of permutations of a model number by filling in wildcards with actual values.
    /// </summary>
    /// <remarks>
    /// This algorithm does not have the best performance as we parse and calculate the options in each iteration.
    /// I suppose the options and literals could be a list so that we only need to process once and then use the sequence
    /// to fill in values.
    /// </remarks>
    /// <param name="input"></param>
    /// <returns></returns>
    public static IEnumerable<string> GetPermutations(string input)
    {
        var countPermutations = CountPermutations(input);

        if (countPermutations <= 1)
        {
            yield return input;
        }
        
        input = WildcardConverter.Normalize(input);

        for (int sequence = 0; sequence < countPermutations; sequence++)
        {
            yield return GetPermutation(input, sequence);
        }
    }

    private static string GetPermutation(string input, int sequence)
    {
        var permutation = new StringBuilder(string.Empty);
        var inParens = false;
        var optionFillers = new List<string>();
        var currentOption = "";
        foreach (var chr in input)
        {
            if (chr.IsFillerWildcard())
            {
                var fillCount = chr.GetFillCount();
                var fillerIndex = sequence % fillCount;
                sequence = sequence / fillCount;
                permutation.Append(WildcardData.FillCharacters[fillerIndex]);
            }
            else if (chr == '(')
            {
                inParens = true;
                optionFillers = new List<string>();
                optionFillers.Add(""); // blank option
                currentOption = "";
            }
            else if (chr == ',')
            {
                optionFillers.Add(currentOption);
                currentOption = "";
            }
            else if (chr == ')')
            {
                inParens = false;
                optionFillers.Add(currentOption);
                var fillerIndex = sequence % optionFillers.Count;
                permutation.Append(optionFillers[fillerIndex]);
            }
            else
            {
                if (inParens)
                {
                    // Parse multi-character option
                    currentOption += chr;
                }
                else
                {
                    // Tack on a literal value
                    permutation.Append(chr);
                }
            }
        }

        return permutation.ToString();
    }
2
  • What if you loop through the ModelNumbers and evaluate the user input by using the KMP algorithm (for detecting patterns on a text). You could then adjust the algorithm to consider the wildcards, for when the algorithm compares two characters of the two strings. If you think this might help I can try to write a more elaborate answer. Commented Dec 13, 2021 at 17:43
  • 1
    That is SUPER COOL! I have never heard of KMP before. Thank you @EmersonCardoso! I looked it up on wikipedia. It seems like a similar algorithm (maybe) to the C# string Contains method. Actually I did end up writing something similar to that a few days ago. Basically a Contains method that allows for wildcards. I clean up the model number first, so I only have 1 wildcard character to deal with.
    – Jess
    Commented Dec 13, 2021 at 18:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.