Functional programming techniques, which may seem a little imposing at first, often make problems like this simpler. Using Ramda (disclosure: I'm one of the authors; but these techniques are available in many other functional programming libraries) you can do things like this:
var countAvailable = R.pipe(R.filter(R.propEq('available', 'true')), R.prop('length'));
var pagesRead = R.pipe(R.pluck('current_page'), R.sum);
var maxTitleLength = R.pipe(R.pluck('title'), R.pluck('length'), R.max);
Each of these returns a function which you can call with bookarrays. You can see it in action on the Ramda REPL.
The main point is that a common function like pluck gives you the chance to change one list into another. So
pluck('title')(bookarrays);
//=> ["Fast Cars", "Slow Cars", "Big Cars", "Small Cars", "Cars"]
Then you have a simpler list for further manipulation. If you call pluck('length') on that you get [9. 9, 8, 10, 4], and then you can call max on that. So just using pluck and max and pipeing them together, you can create a function maxTitleLength quite easily. Functional programming does this a lot: creating simple composable functions that can be used on the same data structures (in this case, lists)
Update
The point of all this is not the library itself. The idea is that if you make a number of small, reusable, composable pieces, you can build up your more complex functions out of them. Here is the beginning of such a collection of functions, enough to solve those problems:
var add = function(a, b) {
return a + b;
};
var sum = function(list) {
return list.reduce(add, 0);
};
var max = function(list) {
return Math.max.apply(Math, list);
};
var map = function(fn) {
return function(list) {
return list.map(fn);
};
};
var prop = function(name) {
return function(obj) {
return obj[name];
};
};
var pipe = function() {
var funcs = arguments;
return function() {
var args = arguments;
for (var i = 0; i < funcs.length; i++) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
};
var pluck = pipe(prop, map);
var filter = function(fn) {
return function(list) {
return list.filter(fn);
};
};
var propEq = function(name, val) {
return function(obj) {
return obj[name] === val;
};
}
To use this little library, you can then write your code by combining them. Note how simple the definition of pluck is.
You could also write it as this:
var pluck = function(name) {
return map(prop(name));
};
But since pipe handles combining functions in that way, we can simply write
var pluck = pipe(prop, map);
Obviously, using these functions, you can then write functions for your answers like this:
var countAvailable = pipe(filter(propEq('available', 'true')), prop('length'));
var pagesRead = pipe(pluck('current_page'), sum);
var maxTitleLength = pipe(pluck('title'), pluck('length'), max);
countAvailable(bookarrays); //=> 3
pagesRead(bookarrays); //=> 192
maxTitleLength(bookarrays); //=> 10
Of course this is not the easiest way to complete a task like this. But now you have all those functions available, and further tasks would be easier. That's what functional programming is about.
This skips one important tool in functional programming, called currying. There's a great article by Hugh Jackson on the topic, and I wrote another one with additional details. I won't go into details here, but it would make those functions simpler. For instance, instead of
var propEq = function(name, val) {
return function(obj) {
return obj[name] === val;
};
}
we could just write
var propEq = curry(function(name, val, obj) {
return obj[name] === val;
});
and you could call it as above, passing only name and val to get back a function that takes obj. Or you could pass it all three parameters right away:
propEq('length', 3, 'abc'); //=> true
Using a curry function would simplify the above code quite a bit further and simultaneously make it more flexible.
trueorfalsevalues?