I am trying to create an array of category objects from an array of materialized category paths.
var data = [
'Business / Finance',
'Business / Management',
'Business / Management / Leadership',
'Business / Team / Leadership'
];
// Expected results:
var result = [
{ name: 'Business', trail: null, path: 'Business' },
{ name: 'Finance', trail: 'Business', path: 'Business / Finance' },
{ name: 'Management', trail: 'Business', path: 'Business / Management' },
{ name: 'Leadership', trail: 'Business / Management', path: 'Business / Management / Leadership' },
{ name: 'Team', trail: 'Business', path: 'Business / Team / Leadership' },
{ name: 'Leadership', trail: 'Business / Team', path: 'Business / Team / Leadership' }
];
As you can see, Business
should only be present once, since all others are only subcategories. However, Leadership
should be present twice, because both are in a different structure.
When you check out the fiddle http://jsfiddle.net/9uC9Z/ you can see that Business
exists 4 times.
How can I solve the problem?
I would really appreciate code comments if the resulting code is very complex.
Edit:
The materialized path strings in the data
array reflect category hierarchies for books. An example would be:
{
title: 'Leadership 101',
author: 'John Smith',
category: 'Business / Management / Leadership'
}
That just represents one book. I now want to create one MongoDB document for every category. Above sample book would produce three category objects (Business, Management, Leadership). However, if a category (or a subcategory) object/document already exists, I don't need to create another one.
result
therefore represents the category objects I will store inside my MongoDB collection. (I will add relationships between the categories, but that isn't part of the current problem.)
Functional approach:
function extract (path, trail) {
if (path.length === 0) {
return [];
}
var item = {
name: path[path.length - 1],
trail: trail.length === 0 ? null : trail.join(' / '),
path: path.join(' / ')
};
var result = extract(path.slice(0, -1), path.slice(0, -2)).concat([item]);
return result;
}
function distinct (xs) {
function eq (a, b) {
return JSON.stringify(a) === JSON.stringify(b);
}
function contains (xs, x) {
for (var i = xs.length - 1; i >= 0; i--) {
if (eq(xs[i], x)) {
return true;
}
}
return false;
}
var result = [];
for (var i = xs.length - 1; i >= 0; i--) {
if (!contains(result, xs[i])) {
result.push(xs[i]);
}
}
return result;
}
var result = data.
map(function(x) { return x.split(' / ') }).
map(function(x) { return extract(x, x.slice(0, -1)) }).
reduce(function(a, b) { return a.concat(b)});
result = distinct(result);
You can replace distinct
function with something more robust from some library. And be careful using JSON.stringify(a) === JSON.stringify(b)
for object equality in other places. You can read more about it here How to determine equality for two JavaScript objects?