JavaScript Arrays
part II
http://slides.com/tuxsudo/javascript-arrays/live#/
"YOU ONLY REALLY NEED TO KNOW 4 METHODS TO BE SUPER EFFECIENT [SIC]:
forEach, map, filter, reduce"
-- Cory Brown
"ARMED WITH THESE TOOLS, YOU ARE NOW PREPARED TO ENTER THE WORLD OF FUNCTIONAL PROGRAMING. [sic] "
-- Cory Brown
KNOWS WAY TOO MUCH ABOUT EVERYTHING
NEVER HEARD OF SPELCHECKR
Refresher
[].forEach()
Execute
[].map()
Transform
[].filter()
Um, Filter
[].reduce()
Calculate
execute against each item
transform each item
get rid of some items
reduce to a single, calculated value
[].forEach
function(item, i, original)
[].map
[].filter
function(previous, item, i, original)
[].reduce
undefined
[]
[]
//anything
function(item, i, original)
function(item, i, original)
//anything
boolean
//anything
method
returns
expects callback
callback should return
are optional
i
original
&
and usually excluded
Two More, just for fun
[].every()
Test All
[].some()
Test for 1
all items must pass
at least 1 item must pass
[].every
[].some
boolean
boolean
function(item, i, original)
boolean
boolean
function(item, i, original)
method
returns
expects callback
callback returns
[].forEach
function(item, i, original)
[].map
[].filter
function(previous, item, i, original)
[].reduce
undefined
[]
[]
//anything
function(item, i, original)
function(item, i, original)
//anything
boolean
//anything
method
returns
expects callback
callback returns
[].every
[].some
boolean
boolean
function(item, i, original)
boolean
boolean
function(item, i, original)
are optional
i
original
&
and usually excluded
Some Examples
var favs = ['pizza', 'cheeseburgers', 'bacon'],
results;
function addToPage(food) {
var div = document.createElement('div');
div.innerHTML = food;
document.body.appendChild( div );
}
results = favs.forEach(addToPage);
console.log(results);
[].forEach
var favs = ['pizza', 'cheeseburgers', 'veggies', 'bacon'],
results;
function critique(item, i, original) {
if(item==="veggies") {
return "No rabbit food for me, thanks!"
} else {
return item + " is good";
}
}
results = favs.map(critique);
console.log(results);
[].map
[].map
var dimensions = [
{w:10,h:20,d:10},
{w:3,h:2,d:20},
{w:4,h:1,d:400},
{w:9000,h:3999,d:9191}
];
function area(dimension) {
return dimension.w * dimension.h;
}
function volume(dimension) {
return dimension.w * dimension.h * dimension.d;
}
[].filter, [].some, [].every
var favs = ['pizza', 'peas', 'cheeseburgers',
'carrots', 'bacon', 'spinach'];
function all(item) {
return false;
}
function none(item) {
return true;
}
function veggie(item) {
return /peas|carrots|spinach/.test(item);
}
function noVeggie(item) {
return !/peas|carrots|spinach/.test(item);
}
[].reduce
More on [].reduce
[].reduce( function(previous, item, i, original), unshiftVal);
function sum(previous, item) {
return previous + item;
}
balance = payments.reduce(sum, loan);
var loan = -800,
payments = [100.44, 111.32, 44.11, 54.33, 12.11],
balance;
[-800, 100.44, 111.32, 44.11, 54.33, 12.11]
1. "unshift"
unshiftVal
(conceptually)
[].reduce( function(previous, item, i, original), unshiftVal);
// loan = -800
// payments = [100.44, 111.32, 44.11, 54.33, 12.11]
// payments.reduce(sum, loan);
+
[-800, 100.44, 111.32, 44.11, 54.33, 12.11]
-800
-699.56
+ 111.32
-588.24
+ 44.11
-544.13
+ 54.33
-489.80
+ 12.11
-477.69
function sum(previous, item) {
return previous + item;
}
+100.44
var favs = [
['pizza', 'italian', 'mexican', 'veggies'],
['italian', 'mexican', 'noodles'],
['italian', 'chinese', 'pizza'],
['cheeseburgers', 'bacon', 'fruit'],
['salad', 'mushrooms', 'mexican'],
['salad', 'bacon', 'fruit'],
['sugar', 'chinese', 'pizza'],
['pizza']
];
function tallyFavs(previous, item) {
item.forEach( function addTally(food) {
previous[food] = previous[food] && previous[food]+1 || 1;
});
return previous;
}
[].reduce
favs.reduce( tallyFavs, {} );
var weights = [150, 140, 220, 300, 690, 844, 230, 12, 43];
function toAvg(last, current, i, original) {
var total = last + current;
return i+1 < original.length ? total : total / original.length;
}
[].reduce
weights.reduce( toAvg );
Now
the fun part
Friend Schema
{
"name": "Jane",
"age": 30,
"gender": "female",
"weight": 140,
"married": true,
"kids": 4,
"location": {
"lat": 40.511841544251176,
"lon": -111.98006585240364
},
"favs": {
"foods": [],
"movies": [],
"books": []
},
"tags": []
}
var friends = friendService.findFriends();
// Giant Array of Friends
Some Functions
// Some Filters
function hasKids(item) {
return item.kids && item.kids > 0;
}
function eatsPizza (item) {
return item.favs.food.indexOf('pizza') >= 0;
}
// Some Maps (accepts full person object, returns only their location)
function attributeLocations (item) {
return item.location;
}
function attributeAges (item) {
return item.age;
}
// A Reduction
function toAverage (last, item, i, original) {
var sum = last + item;
return i+1<original.length ? sum : sum/original.length;
}
// An executable (forEach)
function logName(item) {
console.log(item.name);
}
friends.forEach(logName);
friends.filter(hasKids).forEach(logName);
friends
.filter(hasKids)
.filter(eatsPizza)
.forEach(logName);
friends.filter(hasKids).map(attributeAges);
Demo Time
Observations:
- functions are reusable and can be composed in many orders
- pretty powerful for only a few functions
- we're just getting started..
G O I N G
D E E P E R
A Higher Order
// Some Filters
function eatsPizza (item) {
var foods = item.favs.food;
return foods.indexOf('pizza') >= 0;
}
function eatsItalian (item) {
var foods = item.favs.food;
return foods.indexOf('italian') >= 0;
}
function eatsMexican
function eatsChinese
function eatsWhatever
// --> to infinity?! F T L
function eats(food) {
return function(item) {
return item.favs.food.indexOf(food) >= 0;
};
}
// then:
friends.filter( eats('pizza') );
friends.filter( eats('chinese') );
friends.filter( eats('vegetables') );
// --> Infinity F T W
This can go on forever. Write a new function
every time someone adds a new favorite
type of food?
function eats(food) {
return function(item) {
return item.favs.food.indexOf(food) >= 0;
};
}
[].filter
boolean function(item, i, original)
expects callback
Remember?!:
AND:
JavaScript functions can create and return other functions
function eatsPizza (item) {
var foods = item.favs.food;
return foods.indexOf('pizza') >= 0;
}
like:
like:
function eats(food) {
return function(item) {
return item.favs.food.indexOf(food) >= 0;
};
}
And in this case, the "signature" of the returned function matches the "signature" of what [].filter expects for its callback
eg:
function(item) {
return item.favs.food.indexOf('pizza') >= 0;
}
eats('pizza')
function(item) {
return item.favs.food.indexOf('chinese') >= 0;
}
eats('chinese')
function(item) {
return item.favs.food.indexOf('vegetables') >= 0;
}
eats('vegetables')
which is plopped into [].filter(
)
etc.
function attributeLocations (item) {
return item.location;
}
function attributeAges (item) {
return item.age;
}
// --> Sadly to infinity
same for our [].map functions
function toAttribute(attribute) {
return function(item) {
return item[attribute];
}
}
// then:
friends.map(toAttribute('age'));
friends.map(toAttribute('location'));
// --> Happily to infinity
Putting it all together
function eats(food) {
return function(item) {
return item.favs.food.indexOf(food) >= 0;
};
}
function has(attribute) {
return function(item) {
return item[attribute] && true || false;
}
}
function min(attribute, value) {
return function(item) {
return item[attribute] >= value;
}
}
function max(attribute, value) {
return function(item) {
return item[attribute] <= value;
}
}
function withTag(tag) {
return function(item) {
return item.tags.indexOf(tag) >= 0;
};
}
More Better Filters
friends.filter(eats('pizza'));
friends.filter(has('distance'));
friends.filter(min('age', 35));
friends.filter(max('kids', 5));
friends.filter(withTag('bff'));
function negate(fn) {
return function(item) {
return !fn(item);
};
}
friends.filter(negate(eats('pizza')));
Which friends don't eat pizza?
function dontEats(food) {
return function(item) {
return item.favs.food.indexOf(food) < 0;
};
}
friends.filter(dontEats('pizza'));
Negate ALL THE THINGS!!
friends.filter(negate(withTag('bff')));
function toAttribute(attribute) {
return function(item) {
return item[attribute];
}
}
More Better Map
friends.map(toAttribute('name'));
function setAttribute(attribute, value) {
return function(item) {
var cloned = clone(item);
cloned[attribute] = value;
return cloned;
}
}
friends.map(
setAttribute('iterated', true)
);
function addTag(tag) {
return function(item) {
var cloned = clone(item);
cloned.tags.push(tag);
return cloned;
}
}
friends.map(addTag("bff"));
function removeTag(tag) {
return function(item) {
var cloned = clone(item),
tags = cloned.tags.filter(function(t){
return t !== tag;
});
cloned.tags = tags;
return cloned;
}
}
friends.map(removeTag("bff"));
friends
.filter( eats('pizza') )
.filter( has('kids') )
.map( addTag('invited') )
.forEach( persist );
friends
.filter( eats('veggies') )
.filter( hasTag('bff') )
.map( removeTag('bff') )
.forEach( prankCall );
friends
.filter(negate(has('gender')))
.map(setAttribute('name', 'Pat'))
.map(addTag( 'shady' ))
.forEach(persist);
So Expressive, No?
Function.bind
speaking of functions returning functions
function.prototype.bind()?!
function makeLovers(person1, person2) {
var lover1 = person1,
lover2 = person2;
return lover1 + " & " + lover2 + " sittin' in a tree";
}
function.prototype.bind(thisArg, argument1, argument2);
function
returns
makeLovers("Jared", "Sara");
//Jared & Sara sittin' in a tree
var jaredLoves = makeLovers.bind(null, "Jared");
// function
var jaredLoves = function (person2) {
var lover1 = "Jared",
lover2 = person2;
return lover1 + " & " + lover2 + " sittin' in a tree";
}
==
jaredLoves("Sara");
//Jared & Sara sittin' in a tree
function setDistance(person1, person2) {
var loc1 = person1.location,
loc2 = person2.location,
cloned = clone(person2), // pass through a cloning function to keep pure
radlat1 = Math.PI * loc1.lat/180,
radlat2 = Math.PI * loc2.lat/180,
radlon1 = Math.PI * loc1.lon/180,
radlon2 = Math.PI * loc2.lon/180,
theta = loc1.lon-loc2.lon,
radtheta = Math.PI * theta/180,
dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
dist = Math.acos(dist);
dist = dist * 180/Math.PI;
dist = dist * 60 * 1.1515;
cloned.distance = (dist * 0.8684)
return cloned;
}
friends.map( setDistance.bind(null, me) );
[].map + fn.bind = <3
function alike(person, attribute) {
return function(item) {
return person[attribute]===item[attribute];
}
}
friends.filter( alike(me, 'gender') );
+ another "relative" filter
The End
JavaScript Arrays
By Jared Anderson
JavaScript Arrays
- 1,902