Reputation: 11733
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);
})
})
})
Upvotes: 1
Views: 84
Reputation: 30098
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.
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);
});
});
Upvotes: 2
Reputation: 55678
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
});
Upvotes: 1