javascriptmaterialized-path-pattern

Objects from Materialized Path


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. resulttherefore 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.)


Solution

  • 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?