1

I've just read about reactive programming and I am enthusiast about it. So I decided to revise my skill on functional programming. I don't know if this is the right place.

I have two array, one of tags and one of tasks that contains tags. I'd like to aggregate the two and go out with a tasksByTagName. I've tried to use lodash but I didn't managed to do it in a readable way so I'm posting what I've done with normal for statements.

More over I'm interested in understanding how to think in a flow based way, such as thinking my aggregate function as a transformation between two stream, as the reactive programming article linked above suggest.

So, let's start with datas:

var tags = [ 
  { id: 1, name: 'Week 26' },
  { id: 2, name: 'week 27' },
  { id: 3, name: 'Week 25' },
  { id: 4, name: 'Week 25' }
];

var tasks = [
  {
    "name": "bar",
    "completed": false,
    "tags": [
      { "id": 1 },
      { "id": 2 }
    ]
  },
  {
    "name": "foo",
    "completed": true,
    "tags": [
      { "id": 1 }
    ]
  },
  {
    "name": "dudee",
    "completed": true,
    "tags": [
      { "id": 3 },
      { "id": 1 },
      { "id": 4 }
    ]
  }
];

And this is my piece of code:

var _ = require('lodash');

function aggregate1(tasks, tags) {

  var tasksByTags = {};
  for(var t in tasks) {
    var task = tasks[t];
    for(var i=0; i<task.tags.length; ++i) {
      var tag = task.tags[i];
      if( !tasksByTags.hasOwnProperty(tag.id) ) {
        tasksByTags[tag.id] = [];  
      }
      tasksByTags[tag.id].push(task.name);
    }
  }

  var tagById = {};
  for(var t in tags) {
    var tag = tags[t];
    tagById[tag.id] = tag.name;
  }

  var tasksByTagsName = _.mapKeys(tasksByTags, function(v, k) {
    return tagById[k];
  })

  return tasksByTagsName;
}

module.exports.aggregate1 = aggregate1;

For completeness, this is also the test with test data:

var tags = [ 
  { id: 1, name: 'Week 26' },
  { id: 2, name: 'week 27' },
  { id: 3, name: 'Week 25' },
  { id: 4, name: 'Week 25' }
];

var tasks = [
  {
    "name": "bar",
    "completed": false,
    "tags": [
      { "id": 1 },
      { "id": 2 }
    ]
  },
  {
    "name": "foo",
    "completed": true,
    "tags": [
      { "id": 1 }
    ]
  },
  {
    "name": "dudee",
    "completed": true,
    "tags": [
      { "id": 3 },
      { "id": 1 },
      { "id": 4 }
    ]
  }
];

var goodResults1 = {
  'Week 26': [ 'bar', 'foo', 'dudee' ],
  'week 27': [ 'bar' ],
  'Week 25': [ 'dudee' ] 
};    



var assert = require('assert')

var aggregate1 = require('../aggregate').aggregate1;

describe('Aggegate', function(){
  describe('aggregate1', function(){
    it('should work as expected', function(){
      var result = aggregate1(tasks, tags);
      assert.deepEqual(goodResults1, result);
    })
  })
})
4
  • Do you want goodResults or goodResults1? Commented Jul 1, 2015 at 21:43
  • @BrandonSmith It seems goodResults is dead code since it's not used anywhere. The unit test uses goodResults1. Commented Jul 1, 2015 at 21:47
  • Agreed, just wanted to confirm. @Sylwester Commented Jul 1, 2015 at 21:48
  • yes, sorry, removed! It was part of a previous test. Commented Jul 1, 2015 at 21:55

2 Answers 2

2

This approach below translates to an object reduction from the tags object. With the help of lazy evaluation, the process below only runs tags.length x tasks.length times. If you're unfamiliar with lazy evaluation, here's a reference.

Example Here

script.js

function agregateTasks(tasks, tags) {

  return _.reduce(tags, function(result, tag) {

    // chain tasks to lazy evaluate
    result[tag.name] = _(tasks) 
      // get all tasks with tags that contains tag.id
      .filter({ tags: [_.pick(tag, 'id')]}) 
      // get all names
      .pluck('name') 
      // concatenate existing task names if there are
      .concat(result[tag.name] || []) 
      // only unique task name values
      .uniq() 
      // run lazy evaluation
      .value(); 

    return result;

  }, {});

}

script.spec.js

describe('aggregate()', function(){

  var tags, tasks, expectedResults;

  beforeEach(function() {
    tags = [ 
      { id: 1, name: 'Week 26' },
      { id: 2, name: 'week 27' },
      { id: 3, name: 'Week 25' },
      { id: 4, name: 'Week 25' }
    ];

    tasks = [
      {
        "name": "bar",
        "completed": false,
        "tags": [
          { "id": 1 },
          { "id": 2 }
        ]
      },
      {
        "name": "foo",
        "completed": true,
        "tags": [
          { "id": 1 }
        ]
      },
      {
        "name": "dudee",
        "completed": true,
        "tags": [
          { "id": 3 },
          { "id": 1 },
          { "id": 4 }
        ]
      }
    ];

    expectedResults = {
      'Week 26': [ 'bar', 'foo', 'dudee' ],
      'week 27': [ 'bar' ],
      'Week 25': [ 'dudee' ] 
    };

  });

  it('should work as expected', function() {

    var result = aggregateTasks(tasks, tags);
    expect(result).toEqual(expectedResults);

  });

});
Sign up to request clarification or add additional context in comments.

Comments

1

Here's one approach: https://jsfiddle.net/ok6wf7eb/

function aggregate(tasks, tags) {

    // make an id: tag map
    var tagMap = tags.reduce(function(map, tag) {
        map[tag.id] = tag.name;
        return map;
    }, {});

    // collect tasks by tag
    return tasks.reduce(function(map, task) {
        // loop through task tags
        task.tags.forEach(function(tag) {
            // get tag name by id
            var tagId = tagMap[tag.id];
            // get array, making it on the fly if needed
            var tasks = map[tagId] || (map[tagId] = []);
            // add the new task name
            tasks.push(task.name);
        });
        return map;
    }, {});

}

This uses reduce as a slightly more "functional" approach to the problem, though for legibility I'm never certain there's much gain over

var collector = {};
items.forEach(function(item) {
    // add to collector
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.