How to make a recursive reduction function inside an object?

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

[ { "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": [] }, ... ] }, ... ] 

in it:

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

I train with map and reduce , so I try to avoid for loops (the 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 had to call jobNewPage.buildArrayForSelect(children) to do this recursively. I tried calling this.buildArrayForSelect(children) , but the context is different. I believe that this is not the best option, because I do not want to depend on calling a global variable inside a function in an object. How can I improve it?

+7
javascript recursion reduce
source share
4 answers

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 an object property with a higher scope.

The simple answer is to turn it into an expression with a named function. Such functions can 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 property name obj.myMethod(5); 

This approach, applied to your object and function, will look like this:

 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; }, []); } } 
+5
source share

An example of using Array.prototype.reduce in combination with Array.prototype.concat 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>'); 
+3
source share

You can do this as an expression with a derived function. Assuming your tree structure is in the "tree" variable,

 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); 
0
source share

Try the following:

 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 data:

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

Jsfiddle

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

0
source share

All Articles