简体   繁体   中英

Convert a recursive function to an iteration in javascript

I don't manage to convert a recursive function to an iteration. Consider the following function :

function getTreeDataFromRows (id, rows) {
  let ret_val = []

  for (let i = 0; i < rows.length; i++) {
    let row = rows[i]

    if (id == row.id_parent) {
      let new_element = {
        id:         row.id,
        id_parent:  row.id_parent,
        value:      row.value,
        data:       getTreeDataFromRows(row.id, rows)
      }

      for (let property in row) {
        if ('id' == property || 'id_parent' == property || 'value' == property) {
          continue
        }
        new_element[property] = row[property]
      }

      ret_val.push(new_element)
    }
  }

  return ret_val
}

I have as an input a json similar to this:

[{
    "id": "c-1",
    "id_parent": null,
    "value": "Chapter 1"
  },
  {
    "id": "a-1",
    "id_parent": "c-1",
    "value": "Article 1.1"
  },
  {
    "id": "a-2",
    "id_parent": "c-1",
    "value": "Article 1.2"
  },
  {
    "id": "c-2",
    "id_parent": null,
    "value": "Chapter 2"
  },
  {
    "id": "a-21",
    "id_parent": "c-2",
    "value": "Article 2.1"
  },
  {
    "id": "a-22",
    "id_parent": "c-2",
    "value": "Article 2.2"
  },
  {
    "id": "a-221",
    "id_parent": "a-22",
    "value": "Quote 221 from article 2.2"
  },
  {
    "id": "a-222",
    "id_parent": "a-22",
    "value": "Quote 222 from article 2.2"
  }
]

The output has to be like this:

[{
    "id": "c-1",
    "id_parent": null,
    "value": "Chapter 1",
    "data": [{
        "id": "a-1",
        "id_parent": "c-1",
        "value": "Articole 1.1",
        "data": []
      },
      {
        "id": "a-2",
        "id_parent": "c-1",
        "value": "Articole 1.2",
        "data": []
      }
    ]
  },
  {
    "id": "c-2",
    "id_parent": null,
    "value": "Chapter 2",
    "data": [{
        "id": "a-21",
        "id_parent": "c-2",
        "value": "Articole 2.1",
        "data": []
      },
      {
        "id": "a-22",
        "id_parent": "c-2",
        "value": "Articole 2.2",
        "data": [{
            "id": "a-221",
            "id_parent": "a-22",
            "value": "Quote 221 from article 2.2",
            "data": []
          },
          {
            "id": "a-222",
            "id_parent": "a-22",
            "value": "Quote 222 from article 2.2",
            "data": []
          },
        ]
      },
    ]
  }
]

This output is needed for a treetable. The recursive function gives a "Maximum call stack size exceeded" error when processing a large amount of data. Also the tree can have a large number of children (son, grandson, etc.). I tried to write a for loop, using a stack array, but I didn't succeed. I am rather confused and my code could be confusing as well.

function functionWithIteration (rows) {
  var my_stack = [null]
  var final_val = []

  while( my_stack.length > 0 ) {
    var ret_val = []
    var first_time = true
    var id = my_stack.pop()
    var temp_val = []
    for (let i = 0; i < rows.length; i++) {

      var row = rows[i]
      var signal = true
      if (id == row.id_parent) {
        signal = false
        var new_element = {
          id: row.id,
          id_parent: row.id_parent,
          value: row.value,
          data: []
        }

        for (let property in row) {
          if (property == 'id' || property == 'id_parent' || property == 'value') {
            continue
          }
          new_element[property] = row[property]
        }

        if (first_time){
          ret_val.push(new_element)
          first_time = false
        }
        else {
          ret_val[ret_val.length - 1].data.push(new_element)
        }
      }
      if ( signal) {
        temp_val.push(ret_val)
        my_stack.push(row.id)
      }
    }
    final_val.push(temp_val)
  }

  return final_val
}

Any help would be highly appreciated! Thank you!

You could take a single loop approach with an object for known id and parents.

This solution does not expect data in sorted order.

 var data = [{ id: "c-1", id_parent: null, value: "Chapter 1" }, { id: "a-1", id_parent: "c-1", value: "Article 1.1" }, { id: "a-2", id_parent: "c-1", value: "Article 1.2" }, { id: "c-2", id_parent: null, value: "Chapter 2" }, { id: "a-21", id_parent: "c-2", value: "Article 2.1" }, { id: "a-22", id_parent: "c-2", value: "Article 2.2" }, { id: "a-221", id_parent: "a-22", value: "Quote 221 from article 2.2" }, { id: "a-222", id_parent: "a-22", value: "Quote 222 from article 2.2" }], tree = function (data, root) { var o = {}; data.forEach(function (a) { if (o[a.id] && o[a.id].children) { a.children = o[a.id].children; } o[a.id] = a; o[a.id_parent] = o[a.id_parent] || {}; o[a.id_parent].children = o[a.id_parent].children || []; o[a.id_parent].children.push(a); }); return o[root].children; }(data, null); console.log(tree); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

It is a rather simple solution once you figure out the simple step of holding references to the objects. Loop over the array and create an object with the element id as the key. You than use that to reference the element to add its children.

 var data = [{ "id": "c-1", "id_parent": null, "value": "Chapter 1" }, { "id": "a-1", "id_parent": "c-1", "value": "Article 1.1" }, { "id": "a-2", "id_parent": "c-1", "value": "Article 1.2" }, { "id": "c-2", "id_parent": null, "value": "Chapter 2" }, { "id": "a-21", "id_parent": "c-2", "value": "Article 2.1" }, { "id": "a-22", "id_parent": "c-2", "value": "Article 2.2" }, { "id": "a-221", "id_parent": "a-22", "value": "Quote 221 from article 2.2" }, { "id": "a-222", "id_parent": "a-22", "value": "Quote 222 from article 2.2" } ] // we use reduce to loop over the object to build up our new object. var result = data.reduce((obj, itm) => { // store it into a obj so we can reference it obj.temp[itm.id] = itm // check to see if we have a parent if (itm.id_parent) { // if we have a parent see if data is set yet // if not, set it to an empty array obj.temp[itm.id_parent].data = obj.temp[itm.id_parent].data || [] // push the child into the parent obj.temp[itm.id_parent].data.push(obj.temp[itm.id]) } else { // If we have no parent, than it is a root element // or we push it into an array to keep track of it obj.order.push(obj.temp[itm.id]) } // return the object for reduces next iteration return obj }, { temp:{}, order:[]}) // init recude with an empty object and array .order // return the order console.log(result) 

This solution is expecting that the parents appear before their children. If that is not the case, than there are a couple of things you can do. Either sort of you create a "temp" object until you find the real one.

I think there's an error in your logic. When it recurses, it appears to start over from the beginning. Intuitively this would allow the recursive instance to stumble into exactly the same condition as before and thus to recurse ad infinitum. (In terms of the logic, multiple nested instances wind up with the same value for the loop-variable i .)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM