fesja
fesja

Reputation: 3313

How to do a recursive reduce function inside an object?

I am doing this on the client with Javascript. I want to transform:

[
  {
    "id": 10,
    "name": "Designer",
    "slug": "designer",
    "children": [
      {
        "id": 11,
        "name": "UI / Visual Designer",
        "slug": "ui-visual-designer",
        "children": []
      },
      ...
    ]
  },
  {
    "id": 1,
    "name": "Software Engineer",
    "slug": "software-engineer",
    "children": [
      {
        "id": 2,
        "name": "Back-End Developer",
        "slug": "back-end-developer",
        "children": []
      },
      ...
    ]
  },
  ...
]

into this:

[
  {
    "id": 10,
    "text": "Designer"
  },
  {
    "id": 11,
    "text": "UI / Visual Designer",
  },
  {
    "id": 1,
    "text": "Software Engineer",
  },
  {
    "id": 2,
    "text": "Back-End Developer",
  }
  ...
]

I am practicing with map and reduce so I am trying to avoid for loops (first thing I did). This is the current code I have:

var jobNewPage = {
    ...
    buildArrayForSelect(array) {
        "use strict";
        return $.extend(true, [], array).reduce(function(total, item) {
            if ( item.slug == 'remote' ) return total;

            total.push({
                'id'   : item.id,
                'text' : item.name
            });

            let children = item.children;
            if (children && children.length) {
                // TODO: We had to call the global context jobNewPage
                total = total.concat(jobNewPage.buildArrayForSelect(children));
            }
            return total;
        }, []);
    },
    ...
}

So, as you can see, I have had to call jobNewPage.buildArrayForSelect(children) to do it recursively. I tried to call this.buildArrayForSelect(children) but the context is different. I feel this is not the best option because I don't want to depend calling a global variable inside a function in the object. How can I improve it?

Upvotes: 9

Views: 6191

Answers (4)

Inigo Flores
Inigo Flores

Reputation: 4469

Try this:

Array.prototype.flatten = function () {
    return this.reduce(function (acc, value) {
       acc.push(value);
       acc = acc.concat(value.children.flatten());
       return acc;
    }, []);
};

Array.prototype.extractData = function () {
   return this.map(function(a) {
       return (a.slug!='remote')?{'id':a.id,'text':a.name}:false
   }).filter(function(a) {
       return (a!=false)?a:false;
   });
};

To extract the data:

options=array.flatten().extractData();

JsFiddle

I've just realized that my answer follows a similar approach as James Thorpe's answer (especially the solution suggested by dfsq in a comment). However, his implementation seems far more efficient than mine.

Upvotes: 1

James Thorpe
James Thorpe

Reputation: 32212

It seems your question boils down to how to recursively call a function from within itself, when that function is defined using a function expression and assigned to a property on a higher-scoped object.

The simple answer is to turn it into a named function expression. Such functions are are able to call themselves recursively:

var obj = {
   myMethod: function myName(n) { //function expression has name "myName"...
     console.log(n);
     if (n > 0)
       myName(n-1); //...which we can use inside the function...
   }
}

//...and outside we refer to the object's property name
obj.myMethod(5);

This approach, applied to your object and function, would look as follows:

var jobNewPage = {
    //give the function expression a name:
    buildArrayForSelect: function buildArrayForSelect(array) {
        "use strict";
        return $.extend(true, [], array).reduce(function(total, item) {
            if ( item.slug == 'remote' ) return total;

            total.push({
                'id'   : item.id,
                'text' : item.name
            });

            let children = item.children;
            if (children && children.length) {
                //No need to reference object to call the function recursively:
                total = total.concat(buildArrayForSelect(children));
            }
            return total;
        }, []);
    }
}

Upvotes: 5

Nina Scholz
Nina Scholz

Reputation: 386680

Example how to use Array.prototype.reduce in combination with Array.prototype.concat in a recursive way.

var data = [{ "id": 10, "name": "Designer", "slug": "designer", "children": [{ "id": 11, "name": "UI / Visual Designer", "slug": "ui-visual-designer", "children": [] }] }, { "id": 1, "name": "Software Engineer", "slug": "software-engineer", "children": [{ "id": 2, "name": "Back-End Developer", "slug": "back-end-developer", "children": [] }] }];

function getAll(array) {
    return array.reduce(function (r, a) {
        r.push({ id: a.id, text: a.name });
        if (a.children && Array.isArray(a.children)) {
            r = r.concat(getAll(a.children));
        }
        return r;
    }, []);
}

document.write('<pre>' + JSON.stringify(getAll(data), 0, 4) + '</pre>');

Upvotes: 3

mcgraphix
mcgraphix

Reputation: 2733

You could do it as an Immediately Invoked Function Expression. Assuming your tree structure is in the variable "tree";

var tree = []; //your original tree stucture

var result = (function(input) {
    var flattened = [];

    var flattener = function(collection) {
        collection.forEach(function(item) {

            flattened.push({id: item.id, text: item.name});

            if (item.children.length > 0) {
                flattener(item.children);   
            }
        });
    }

    flattener(input);

    return flattened;
})(tree);

console.log(result);

Upvotes: 0

Related Questions