Skip to content

Instantly share code, notes, and snippets.

@vincentwoo
Last active August 29, 2015 14:07
Show Gist options
  • Save vincentwoo/29e916b89da87d907575 to your computer and use it in GitHub Desktop.
Save vincentwoo/29e916b89da87d907575 to your computer and use it in GitHub Desktop.

Functions and Filtering

One of the more powerful aspects of JavaScript is how it treats functions as first-order members of the language. Variables can be assigned to functions, you make anonymous functions, and you can even inspect the source code of a function.

// Function-as-variable
var adder = function(x, y) {
    return x + y;
};
adder(5, 7); // => 12
// Anonymous (unnamed) functions
function(x, y) {
    return x + y;
}(5, 7); // => also 12
// Inspecting the source or number of args!
var foo = function(x) { console.log(x) };

foo.toString(); // => "function(x) { console.log(x) };"
foo.length; // => 1 (the number of arguments foo takes)

You've also recently used Underscore's filter function to filter lists of stuff:

var evens = _.filter([1, 2, 3, 4, 5, 6], function (num){
    return num % 2 == 0;
}); // => [2, 4, 6]

and even started saving the filter functions themselves as variables:

var evenFilter = function(num) { return num % 2 == 0; };
var oddFilter = function(num) { return num % 2 != 0; };

var odds = _.filter([1, 2, 3, 4, 5, 6], oddFilter);
// => [1, 3, 5]

Filters as Data

But just naming a variable after a function and using it isn't very interesting. We can do better. What if we randomly picked a filter to apply?

var evenFilter = function(num) { return num % 2 == 0; };
var oddFilter = function(num) { return num % 2 != 0; };
var filters = [evenFilter, oddFilter];

var oddOrEvens = _.filter([1, 2, 3, 4, 5, 6], _.sample(filters));
// => either [1, 3, 5] or [2, 4, 6]

In the previous example, we randomly chose a filter to apply from an array of filters. This "list of functions" construct can be very powerful, especially when compositing things that all act on the same kind of data.

Assignment: Combining Filters

Write a function that takes a list of items and a list of filters, and returns the result of applying all the filters to the items.

var list = _.range(1, 21); // integers 1 through 20

var filters = [
  function(num) { return num > 5; },
  function(num) { return num % 2 != 0 },
  function(num) { return num % 3 == 0 }
];

function applyFiltersToList(list, filters) {
    // your turn
}

applyFiltersToList(list, filters); // => [9, 15]

Remember that filter application should modify neither the original data nor filters.

Filters as Data as Filters

In the previous example, whenever you'd want to apply all three filters, you'd need to call applyFiltersToList on your own every time. This is sort of brittle, because it requires that other people's code know about applyFiltersToList. Let's take it one step further.

Let's imagine that instead of three filters, we had one filter that did:

function megafilter(num) {
  return num > 5 && num % 2 != 0 && num % 3 == 0;
}

We could just pass this filter around on its own instead of needing to pass an array of all three individual filters. Let's colloquially call this a megafilter. A megafilter is a filter that is functionally equivalent to the combination of various other filters. It returns true if all of its member filters would have returned true as well.

Assignment: Melding filters together

In the previous example, we hand-crafted a megafilter. However, it'd be really nice if the computer could just take a list of filters and squash them into a usable megafilter without our help.

Write a function that takes a list of filters and returns a new megafilter that is equivalent to running all three filters in succession. This new filter should be usuable anywhere else, on its own, through normal calls to _.filter.

Other users of your code should have no idea if a filter is a "regular filter" or a "megafilter" - because there is no difference, really. A filter is just a function that returns true or false for a given item, and from the outside, other code has no idea if your filter combines other filters or not.

var list = _.range(1, 21); // integers 1 through 20

var filters = [
  function(num) { return num > 5; },
  function(num) { return num % 2 != 0 },
  function(num) { return num % 3 == 0 }
];

function makeMegafilter(filters) {
  // returns a new megafilter. remember, filters are functions
}

// assign our megafilter to a convenient variable
var megafilter = meldFilters(filters);

// running our megafilter on the same list produces the same results
_.filter(list, megafilter); // => [9, 15]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment