4

I have the following code which extends my filter functionality to allow me to search for more than one Zip-code. Ex. (1254,125,145,1455)

Current Code:

if (Post_Code_PAR != null)
{
    String[] PostCode_Array = Post_Code_PAR.Split(',');
    query = from p in query where PostCode_Array.Contains(p.Post_Code) select p;
}

I want to extend this code in a way that if I give it something like (0*) it will find zip codes which starts with 0. If I give it 123* it will give me all zip codes which starts with 123. Thanks a lot.

Hjalmar Z
  • 1,591
  • 1
  • 18
  • 36
Dragon
  • 85
  • 9

3 Answers3

3

use regular expression:

example:

IEnumerable<string> allInputs = new[] {"70000", "89000", "89001", "90001"};
string commaSeparatedPostCodePatterns = @"89000 , 90\d\d\d";

if (string.IsNullOrWhiteSpace(commaSeparatedPostCodePatterns)) 
    return;

string[] postCodePatterns = commaSeparatedPostCodePatterns
                                .Split(',')
                                .Select(p => p.Trim()) // remove white spaces
                                .ToArray();

var allMatches = 
    allInputs.Where(input => postCodePatterns.Any(pattern => Regex.IsMatch(input, pattern)));

foreach (var match in allMatches)
    Console.WriteLine(match);

With such a problem, the initial requirements are very simple but quickly become more and more complex (due to internationalization, exceptions to the rule, some smart tester testing an unexpected limit-case like 90§$% ...). So regular expressions provide the best trade-off simplicity vs. extendability

The regex equivalent to * is .*. But in your case, you would rather need more restrictive patterns and use the \d placeholder matching a single digit. Optional digit would be \d?. Zero or more digits would be \d*. One or more digits would be \d+. Please look at the documentation for more details.

jeromerg
  • 2,997
  • 2
  • 26
  • 36
  • I just used `Console.WriteLine()` to check that it works in a sample program: it writes into the dos console the result. You can ignore the last two lines. Remark: do prefer the linq syntax `myList.Where(...)` instead of `from myItem in myList ...`: In most case, it is more readable. – jeromerg Apr 15 '15 at 12:54
  • see the addition to the response – jeromerg Apr 15 '15 at 13:04
  • If you want to keep the single `* ` in your post code patterns, you can sustitute it on the fly at a the beginning with `myString.Replace("*", ".*")`. That way you keep the power of regex for future extensions and continue avoiding `if` statement. – jeromerg Apr 15 '15 at 13:16
  • You can copy&paste the sample code into the `public static void Main()` entry point of a C# console application, It compiles and runs. And from this point then you can adapt it to your need and test the result. – jeromerg Apr 15 '15 at 13:19
  • Remark: I'm not a regex expert at all. I've just learned to use them a few years ago for a similar need. The best way to learn, that I found, was by using the regexbuddy software. But you can also ask on stackoverflow, maybe in a separate question. – jeromerg Apr 15 '15 at 13:25
  • @Zayed: the `Split()` doesn't remove the spaces after/before the comma. Try removing them. You can remove them systematically by using `.Trim()`. I will add it to the sample. – jeromerg Apr 15 '15 at 13:32
  • One last question in which part of your code Can I user my Parameter ? "Post_code_PAR"? coz you didnit use it at all . I should change the second line to [string commaSeparatedPostCodePatterns = Post_Code_PAR;] – Dragon Apr 15 '15 at 13:40
  • can you check the first answer plz, its the simplest ? – Dragon Apr 15 '15 at 13:48
  • @Zayed: sql `like` is similar to regex in c#. The sample you provided contains two important components: at first, you look for multiple post code patterns (`PostCode_Array`) and second, the search must be performed with wildcards. In SQL it wouldn't be straightforward neither: as you should combine both `ANY` and `LIKE` operators and use a sub-select. – jeromerg Apr 15 '15 at 13:48
  • @Zayed: `PostCode_Array` corresponds to `postCodePatterns` and contains the list of patterns your are looking for. `query` corresponds to `allInputs` and contains the list of objects to filter (in your example containing the property `Post_Code`, and in my sample being the postCode itself) – jeromerg Apr 15 '15 at 14:02
3

You can replace the * with \d* which is the Regex way of saying "any amount of digits", basically. Then run through and filter with Regex.IsMatch().

if (Post_Code_PAR != null)
{
    var PostCode_Array = from p in Post_Code_PAR.Split(',') select String.Format("^{0}$", p.Replace("*", @"\d*"));
    query = (from p in query where PostCode_Array.Any(pattern => Regex.IsMatch(p.Post_Code, pattern)) select p).ToArray();
}

I tried to keep it as close to your code as possible but you could clean it up a bit and use lambda instead. But this should work just as well :)

Hjalmar Z
  • 1,591
  • 1
  • 18
  • 36
2
string Post_Code_PAR = "";
String[] PostCode_Array = Post_Code_PAR.Split(',');
var zipCodeList = query.Where(x => CustomFilter(x.Post_Code, PostCode_Array)).ToList();

private static bool CustomFilter(string code, string[] foundZipCodes)
{
    return foundZipCodes.Any(x =>
   {
       x = x.Trim();
       var text = x.Replace("*", "");
       if (x.EndsWith("*"))
           return code.StartsWith(text);
       if (x.StartsWith("*"))
           return code.EndsWith(text);
       if (x.Contains("*"))
           return false;
       return code.StartsWith(text);
    });

}

now user can find: "0*,*52" etc.

  • Hi Bondaryuk: what about this line "query = from p in query where PostCode_Array.Contains(p.Post_Code) select p;" Should I delete it ? – Dragon Apr 15 '15 at 11:32
  • @Zayed yes, you can. if you want only startwith use var zipCodeList = PostCode_Array.Where(x => x.StartsWith(startWith)); – Bondaryuk Vladimir Apr 15 '15 at 11:38
  • Hi, I donot get what you do this var filter = "*0";?. I have a Parameter called "Post_Code_PAR" which the user enter vales in it. I wanted to filter those vales in case he wrote some thing like 1455* -- > this will get me all vales which start with 1455 – Dragon Apr 15 '15 at 11:51
  • @Zayed sorry, i understood you, now I change answer, now user can found: "*231, 254*" it return wher StartsWith and EndWith – Bondaryuk Vladimir Apr 15 '15 at 13:19
  • you mean if i enter 123* i will get all number which start with 123? coz this is what I want – Dragon Apr 15 '15 at 14:11
  • @Zayed oh... it's my bug, i changed filter – Bondaryuk Vladimir Apr 15 '15 at 14:34
  • @Zayed _you mean if i enter 123* i will get all number which start with 123?_ - yes you are right – Bondaryuk Vladimir Apr 15 '15 at 15:51