8

I have this code which use a lodash _.chain. I would like to simplify the code, not use lodash and do this in another way.

examObjectives = _.chain(objectives)
   .where({ 'examId': exam })
   .uniq(true, 'id')
   .map(function (s): any { return { id: s.id, text: s.text, numberAndText: s.numberAndText }; })
   .value();

Can someone give me some advice on how I could remove the dependency on lodash, the _.chain and code this making maximum use of the available javascript functions that can now be found in new browsers. Note I would like to use the built in filter and map functions and not use any external functions to create the examObjectives.

I hope someone can come up with some ideas. I am not so familiar with javascript so welcome the chance to learn.

  • @elclanrs answer is using 'filter' and 'map' and satisfies the 'modern browser' solution you are looking for. – Patrick Klug Nov 04 '14 at 04:33
  • @PatrickKlug - elclanrs solution uses his own filter functions. I was meaning the built in filter functions. –  Nov 04 '14 at 05:16
  • he uses the built-in [].filter() and [].map functions. the only thing he does is wrap it in his own function so you can use it to chain things together. – Patrick Klug Nov 04 '14 at 08:54
  • What is this syntax `function (s): any { return {` ? – yerforkferchips Nov 17 '14 at 12:11
  • 2
    Many times LoDash is going to be more performant than even built-in functions, so while I applaud the attempt to remove dependencies, make sure that as you do that, you are doing it for the right reasons. Using built-in functions does not necessarily mean better performance. – LocalPCGuy Mar 17 '15 at 14:37

5 Answers5

6

You can start defining regular functions, that take the receiver last for convenience, for example:

var map = function(f, xs) {
  return xs.map(f)
}

var filter = function(f, xs) {
  return xs.filter(f)
}

Then you can build chain using the arguments object:

var methods = {map: map, filter: filter}

var chain = function(xs) {
  return Object.keys(methods).reduce(function(acc, k) {
    acc[k] = function() {
      var as = [].slice.call(arguments)
      return methods[k].apply(this, as.concat([xs]))
    }
    return acc
  },{})
}

Now you can do:

filter(even, map(plus1, [1,2,3])) //=> [2,4]

As well as:

chain([1,2,3]).map(plus1).filter(even) //=> [2,4]

And if the functions were curried, you can also express it as:

var chain = compose(filter(even), map(plus1))
chain([1,2,3]) //=> [2,4]
elclanrs
  • 92,861
  • 21
  • 134
  • 171
  • Is there a way that I could shorten even more using the javascript map and filter functions? Sorry I should have mentioned this earlier but my friend pointed out that these functions might make it more easy. – Samantha J T Star Nov 02 '14 at 06:33
6

I put some stuff together, and the code looks like this:

objectives.filter(function (x) {
    return x.examId == exam;
}).reduce(function (accumulator, current) {
    if (!accumulator.some(function (x) { return x.id == current.id; })) {
        accumulator.push(current);
    }
    return accumulator;
}, []).map(function (x) {
    return {
        id: x.id,
        text: x.text,
        numberAndtext: x.numberAndText
    }
});

("uniquifier" inspired by https://stackoverflow.com/a/17903018/2022183).

You can shorten this code by extracting the comparison of a property to a constant value with a function like this:

function propertyEqualTo(prop, val) {
    return function (x) {
        return x[prop] == val;
    };
}

and your code will read:

return objectives.filter(propertyEqualTo('examId', exam)).reduce(function (accumulator, current) {
    if (!accumulator.some(propertyEqualTo('id', current.id)) {
        accumulator.push(current);
    }
...

Are you sure that you need the uniq() call here anyways? You are matching for an id which should be unique right from the start. If you were able to remove this call your code whould be very short.

Community
  • 1
  • 1
thriqon
  • 2,458
  • 17
  • 23
  • Thank you for your suggestions. I will look carefully into the solution you proposed and try it out soon. –  Nov 04 '14 at 06:36
1

It depends on what you really want to do.

You want to know how to implement lodash features you use with modern browser API's

Use the proposed solutions in this topic.

You want to remove the dependency to lodash

If this is the case, I guess you want that to publish a dependency-free library.

Note that Underscore and Lodash are both under MIT license and so you can simply copy/paste the functions you need and directly embed it into your distributed app / lib / whatever.

You want good performances

Just be aware that using modern browser API's is not always the way to achieve the best performances. Javascript is now very fast (unless you manipulate directly the DOM, check ReactJS presentations) , and it is often faster to use efficiently designed JS code than badly designed native code. Also not all browser API functions are implemented with native code.

By chaining underscore statements, at each step you create an intermediate array, leading to higher memory consumption and garbage collection activity. According to the size of your initial array, it may affect your app performances and it could be better to use imperative code.

A modern technique to handle these intermediate array allocations while keeping a functional programming style is to use transducers. It seels there are 2 main JS implementations for now:

This may be considered as a premature optimization for small arrays, but actually transducers are not very complicated and does not introduce that much complexity. If you want to feel at home you can even use underscore-tranceducers which offers an API very similar to Underscore.

Sebastien Lorber
  • 89,644
  • 67
  • 288
  • 419
  • keep in mind that _.map() is faster than [].map (because it does less work). also, it's not so easy to rip-apart underscore and lodash because they have lots of internal dependencies, but you can get standalone versions of most methods from the mofun.js lib: http://danml.com/mofun/. – dandavis Nov 11 '14 at 02:11
  • There is also an online tool to get a minimal Lodash library with only the functions you need, reducing a lot the size of the JS file. – PhiLho Sep 03 '15 at 09:25
  • Lodash (in its current version, 3+) does lazy chaining, avoiding to create intermediate arrays when possible. See *“Chain” Methods* in the [Lodash documentation](https://lodash.com/docs). – PhiLho Sep 03 '15 at 09:30
  • @PhiLho transducers are also about composability – Sebastien Lorber Sep 03 '15 at 09:50
0
objectives = [{
    id: 1,
    text: 'hello',
    numberAndText: 'sdfafaadsf',
    examId: 'exam'
}, {
    id: 2,
    text: 'hello',
    numberAndText: 'sdfafaadsf',
    examId: 'exam2'
}];
var result = objectives
    .filter(function(item) {
        return item['examId'] === 'exam';
    })
    .map(function(item) {
        return {
            id: item.id,
            text: item.text,
            numberAndText: item.numberAndText
        };
    })
    .reduce(function(prev, curr) {
        var ids = prev.map(function(item) {
            return item.id;
        });
        if (ids.indexOf(curr.id) < 0) {
            prev.push(curr);
        }
        return prev;
    }, []);

with filter map reduce, you can simple achieve this by vanilla js

Sean
  • 2,990
  • 1
  • 21
  • 31
-1

All of the following one-trick functions were taken from mofun.js, something i wrote to be like lodash, but completely modular (no internal depends between methods) and that took full advantage of native methods.

// mofun.js-provided stock utility stand-alone functions:
function where(o,_,__){var t=this;return Object.keys(t).every(function(k,_,__){return o[k]===t[k];});}
function extract(o,_,__){return o[""+this];}
function extractObject(o,_,__){var o2={};this.forEach(function(t,_,__){return o2[t]=o[t];});return o2;}
function groupBy(r,s){var ret={};r.forEach(function(a,_,__){var k=a[s],b=ret[k]||(ret[k]=[]);b.push(a);});return ret;}
function obVals(o){var t=[],n=0;for(var r in o)o.hasOwnProperty(r)&&(t[n++]=o[r]);return t;}

// begin custom code:
obVals( // temp object back into array of values
  groupBy( // group array of objects into a grouped temp object of arrays
    objectives.filter(where, { 'examId': exam }) // only objects with the proper examId
  , "id") // groupBy using "id" peoperty of each object
)
.map(extract, 0) //take first value of each group's array to get unique ids among result objects
.map(extractObject, ["id", "text", "numberAndText"]) // extract some properties into a blank object

the code remains much the same as lodash, except for the part about finding unique objects based on a sub-property (instead of whole object compares), which i accomplished by grouping by that property, then plucking the first element from each group; adjust as needed.

dandavis
  • 16,370
  • 5
  • 40
  • 36