Skip to content

Instantly share code, notes, and snippets.

@joyrexus
Created March 27, 2014 15:40
Show Gist options
  • Save joyrexus/9810424 to your computer and use it in GitHub Desktop.
Save joyrexus/9810424 to your computer and use it in GitHub Desktop.
Summarization methods for JS arrays

Supplementing Array

JavaScript arrays (as of ES5) have a core set of iteration methods (i.e., methods that apply functions to elements in the array).

While underscore and lodash provide a raft of supplemental collection utilities, there are times when ...

  • you're working server side, so ancient browser compatability is not a concern
  • you only need a few extra methods
  • you'd like to use them as if they were native array methods.

For example, groupBy, countBy, and indexBy are extremely convenient tools for many data summarization tasks.

What if you want to use them just as you would map, filter, and reduce?

See array-ext.coffee.md for a demonstration of how Array can be supplemented with the three aforementioned summarization methods and below for usage examples.

Note that we're just adding methods to Array.prototype. This can cause issues with javascript's native for..in (enumeration-based) looping, but doesn't affect coffeescript's analogous constructs due to how these get compiled down.


groupBy

Groups a list's values by a criterion. Pass a function that returns the criterion.

isLessThan = (x) ->                                   # grouping function
  (y) -> y < x

expected = 
  true: [1, 2, 3, 4]
  false: [5, 6, 7, 8]

eq [1..8].groupBy(isLessThan(5)), expected


data = ['x', 'xx', 'y', 'yy', 'zzz']
length = (x) -> x.length                              # grouping function

expected = 
  1: ['x', 'y']
  2: ['xx', 'yy']
  3: ['zzz']

eq data.groupBy(length), expected

countBy

Counts instances of a list that group by a certain criterion. Pass a a function that returns the criterion to count by. Similar to groupBy, but instead of returning a list of values, returns a count for the number of values in that group.

parity = (x) -> if x % 2 == 0 then 'even' else 'odd'  # grouping function

expected = 
  odd: 3
  even: 2

eq [1..5].countBy(parity), expected

indexBy

Indexes the object’s values by a criterion. Similar to groupBy, but for when you know that your index values will be unique.

people = [ 
  { id: 100, name: 'Bob' }
  { id: 101, name: 'Ann' }
  { id: 102, name: 'Rex' }
]

expected = { 
  '100': { id: 100, name: 'Bob' }
  '101': { id: 101, name: 'Ann' }
  '102': { id: 102, name: 'Rex' }
}

eq people.indexBy((p) -> p.id), expected

More!

Underscore has some nice sub-libraries for working with arrays.

If you want functions to build arrays, see array.builders.

If you want functions to take things from arrays, see array.selectors

If you want to do multi-level nesting on array elements (i.e., groupBy with hierarchical grouping criteria) see nest.

groupBy

Groups a list's values by a criterion. Pass a function that returns the criterion to group by.

Array::groupBy = (f) ->
  result = {}
  group(result, f(x), x) for x in @
  result

group = (result, key, value) ->
  if result[key] then result[key].push(value) else result[key] = [value]

countBy

Counts instances of a list that group by a certain criterion. Pass a a function that returns the criterion to count by. Similar to groupBy, but instead of returning a list of values, returns a count for the number of values in that group.

Array::countBy = (f) ->
  result = {}
  count(result, f(x), x) for x in @
  result

count = (result, key, value) ->
  if result[key] then result[key]++ else result[key] = 1

indexBy

Indexes the object’s values by a criterion. Similar to groupBy, but for when you know that your index values will be unique.

Array::indexBy = (f) ->
  result = {}
  index(result, f(x), x) for x in @
  result

index = (result, key, value) ->
  result[key] = value

module.exports = Array
Array = require 'array-ext'
{ok, deepEqual} = require 'assert'
eq = deepEqual
# testing Array::groupBy
isLessThan = (x) -> # grouping function
(y) -> y < x
expected =
true: [1, 2, 3, 4]
false: [5, 6, 7, 8]
eq [1..8].groupBy(isLessThan(5)), expected
data = ['x', 'xx', 'y', 'yy', 'zzz']
length = (x) -> x.length # grouping function
expected =
1: ['x', 'y']
2: ['xx', 'yy']
3: ['zzz']
eq data.groupBy(length), expected
# testing Array::countBy
parity = (x) -> if x % 2 == 0 then 'even' else 'odd' # grouping function
expected =
odd: 3
even: 2
eq [1..5].countBy(parity), expected
# testing Array::indexBy
people = [
{ id: 100, name: 'Bob' }
{ id: 101, name: 'Ann' }
{ id: 102, name: 'Rex' }
]
expected = {
'100': { id: 100, name: 'Bob' }
'101': { id: 101, name: 'Ann' }
'102': { id: 102, name: 'Rex' }
}
eq people.indexBy((p) -> p.id), expected
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment