Javascript Array Filtering

In my last post, I presented a javascript function to convert a C# style lambda expression into a function as part of a library that provides functionality similar to Linq.

Today I’ll add a method for filtering. Linq has a where clause, so I’ll add a where method, callable through the Array prototype.

To specify the filter criteria, I’ll pass a function or lambda expression. I’ll also include support for passing extra parameters through to the filter function.

Our where method signature looks like this:

var where( filter, param,...);

where filter is a function or lambda string and param represents parameters passed through to the filter function.

Our filter function signature looks like this:

var filter( el, i, res, params,... );

where el is the value of the element from the array specified by index, i is the current index, res is the array of filtered elements, and params represents the extra parameters passed to the where call.

The code:

Array.prototype.where =
   function(f)
   {
      var fn = f ;

      // if type of parameter is string         
      if ( typeof f == "string" )
         // try to make it into a function
         if ( ( fn = lambda( fn ) ) == null )
            // if fail, throw exception
            throw "Syntax error in lambda string: " + f ;

      // initialize result array
      var res = [] ;
      var l = this.length;
      // set up parameters for filter function call
      var p = [ 0, 0, res ] ;

      // append any pass-through parameters to parameter array               
      for (var i = 1; i < arguments.length; i++) p.push( arguments[i] );

      // for each array element, pass to filter function
      for (var i = 0; i < l ; i++)
      {
         // skip missing elements
         if ( typeof this[ i ] == "undefined" ) continue ;

         // param1 = array element             
         p[ 0 ] = this[ i ] ;
         // param2 = current indeex
         p[ 1 ] = i ;

         // call filter function. if return true, copy element to results            
         if ( !! fn.apply(this, p)  ) res.push(this[i]);
      }

      // return filtered result
      return res ;
   }

I find that examples sometime help me grasp concepts better than descriptive text, so here a few ways to use the where method:

    var a = [1,2,3,4,5,6,7,8,9,10];

    // return everything
    a.where( "( ) => true" ) ;
        --> [1,2,3,4,5,6,7,8,9,10]

    // return even numbers
    a.where( "( n, i ) => n % 2 == 0" ) ;
        --> [2,4,6,8,10]

    // using product table data from SQL Server's northwind database...    
    var products = [
          {key: 1, prod: "Chai", cat: "Beverages", units: 39, reorderlevel: 10},
          {key: 2, prod: "Chang", cat: "Beverages", units: 17, reorderlevel: 25},
          {key: 3, prod: "Aniseed Syrup", cat: "Condiments", units: 13, reorderlevel: 25},
          //....        
          {key: 75, prod: "Rhönbräu Klosterbier", cat: "Beverages", units: 125, reorderlevel: 25},
          {key: 76, prod: "Lakkalikööri", cat: "Beverages", units: 57, reorderlevel: 20},
          {key: 77, prod: "Original Frankfurter grüne Soße", cat: "Condiments", units: 32, reorderlevel: 15} ] ;

    // query beverages that need reordering using extra parameter
    products.where( "( el, i, res, param ) => el.units <= el.reorderlevel && el.cat == param" , "Beverages");
        --> {key: 2, prod: "Chang", cat: "Beverages", units: 17, reorderlevel: 25}
            {key: 43, prod: "Ipoh Coffee", cat: "Beverages", units: 17, reorderlevel: 25}
            {key: 70, prod: "Outback Lager", cat: "Beverages", units: 15, reorderlevel: 30}

    // query first 6 products whose category begins with 'con' using extra param and regular expression
    products.where( "( el, i, res, param ) => res.length <= 6 && param.test( el.cat )", new RegExp( "^con", "i") );
        --> {key: 3, prod: "Aniseed Syrup", cat: "Condiments", units: 13, reorderlevel: 25}
            {key: 4, prod: "Chef Anton's Cajun Seasoning", cat: "Condiments", units: 53, reorderlevel: 0}
            {key: 5, prod: "Chef Anton's Gumbo Mix", cat: "Condiments", units: 0, reorderlevel: 0}
            {key: 6, prod: "Grandma's Boysenberry Spread", cat: "Condiments", units: 120, reorderlevel: 25}
            {key: 8, prod: "Northwoods Cranberry Sauce", cat: "Condiments", units: 6, reorderlevel: 0}
            {key: 15, prod: "Genen Shouyu", cat: "Condiments", units: 39, reorderlevel: 5}
            {key: 16, prod: "Pavlova", cat: "Confections", units: 29, reorderlevel: 10}

    // using customer table data from SQL Server's northwind database...    
    var customers = [
        {name:"Maria Anders",city:"Berlin",zip:"12209",country:"Germany"},
        {name:"Ana Trujillo",city:"México D.F.",zip:"05021",country:"Mexico"},
        {name:"Antonio Moreno",city:"México D.F.",zip:"05023",country:"Mexico"},
        //...
        {name:"Karl Jablonski",city:"Seattle",zip:"98128",country:"USA"},
        {name:"Matti Karttunen",city:"Helsinki",zip:"21240",country:"Finland"},
        {name:"Zbyszek Piestrzeniewicz",city:"Warszawa",zip:"01-012",country:"Poland"}];

    // query customers in the USA
    customers.where( "( el, i, res, param ) => el.country == param", "USA" );
        --> {name:"Howard Snyder",city:"Eugene",zip:"97403",country:"USA"}
            {name:"Yoshi Latimer",city:"Elgin",zip:"97827",country:"USA"},
            {name:"John Steel",city:"Walla Walla",zip:"99362",country:"USA"}
            {name:"Jaime Yorres",city:"San Francisco",zip:"94117",country:"USA"}
            {name:"Fran Wilson",city:"Portland",zip:"97219",country:"USA"}
            {name:"Rene Phillips",city:"Anchorage",zip:"99508",country:"USA"}
            {name:"Paula Wilson",city:"Albuquerque",zip:"87110",country:"USA"}
            {name:"Jose Pavarotti",city:"Boise",zip:"83720",country:"USA"}
            {name:"Art Braunschweiger",city:"Lander",zip:"82520",country:"USA"}
            {name:"Liz Nixon",city:"Portland",zip:"97201",country:"USA"}
            {name:"Liu Wong",city:"Butte",zip:"59801",country:"USA"}
            {name:"Helvetius Nagy",city:"Kirkland",zip:"98034",country:"USA"}
            {name:"Karl Jablonski",city:"Seattle",zip:"98128",country:"USA"}

I could go on but I need to get started on the next method. The where method does a good job filtering the data, but leaves it in it’s original form. We may need to also modify the filtered data. The select method will allow us to modify the filtered data in much the same way the select clause does in SQL and Linq. I’ll be developing the select method in my next post.

Be Sociable, Share!

Post a comment.

Subscribe without commenting