JavaScript-递归构建树数据结构

时间:2018-11-11 13:24:16

标签: javascript arrays object recursion tree

我有一个称为树的函数,该函数接收对象数组(作为数据库中的数据字段)和键字符串数组。该函数遍历rowsArray,并基于keyArray递归创建具有嵌套属性的对象。

const tree = (rowsArray, keysArray) => {
  return rows.reduce((acc, row) => {
    const groupBy = (row, keys,) => {
      const [first, ...rest] = keys;

      if (!first) return [row];

      return {
        [row[first]]: groupBy(row, rest),
      }
    };
    acc = {...groupBy(row, keys), ...acc};
    return acc;
  }, {});
}

数据如下:

const data = [{
        ID: 1,
        Main: "Financial",
        Sub: "Forecasts",
        Detail: "General"
    }, {
        ID: 2,
        Main: "Financial",
        Sub: "HR",
        Detail: "Headcount"
}];

const result1 = tree(data, ["Main", "Sub", "Detail"]);
console.log(result1); 

当我记录结果时,我得到:

/*
// actual output
  { 
    Financial: { 
      Forecasts:  { 
        General: [Array] 
      } 
    } 
  }

我想得到以下信息:

  // expected
  { 
    Financial: { 
      Forecasts:  { 
        General: [Array] 
      },
      HR:  { 
        Headcount: [Array] 
      }
    } 
  }
  */

问题是,主函数中的acc变量被覆盖,并且我得到了新的对象,而不是累积的,而且我不太确定如何递归地构建该对象。我试图将acc实例传递给groupBy函数(以记住以前的结果),但是没有运气。

您是否知道如何重写树函数或groupBy函数以实现我的目标?谢谢!

4 个答案:

答案 0 :(得分:1)

问题在于您的合并功能不是 deep 。当您将值分配给累加器时,您将覆盖现有属性-在这种情况下为Financial

我包含了一个深层合并功能from here,现在它可以工作了。

我还修复了您遇到的一些参考错误:

  • rows => rowsArray
  • keys = keysArray

// deep merge function
function merge(current, update) {
  Object.keys(update).forEach(function(key) {
    // if update[key] exist, and it's not a string or array,
    // we go in one level deeper
    if (current.hasOwnProperty(key) &&
      typeof current[key] === 'object' &&
      !(current[key] instanceof Array)) {
      merge(current[key], update[key]);

      // if update[key] doesn't exist in current, or it's a string
      // or array, then assign/overwrite current[key] to update[key]
    } else {
      current[key] = update[key];
    }
  });
  return current;
}

const tree = (rowsArray, keysArray) => {
  return rowsArray.reduce((acc, row) => {
    const groupBy = (row, keys, ) => {
      const [first, ...rest] = keys;

      if (!first) return [row];

      return {
        [row[first]]: groupBy(row, rest),
      }
    };
    acc = merge(groupBy(row, keysArray), acc);
    return acc;
  }, {});
}

const data = [{
  ID: 1,
  Main: "Financial",
  Sub: "Forecasts",
  Detail: "General"
}, {
  ID: 2,
  Main: "Financial",
  Sub: "HR",
  Detail: "Headcount"
}];

const result1 = tree(data, ["Main", "Sub", "Detail"]);
console.log(result1);

答案 1 :(得分:1)

您可以这样做:

function tree(rows, keys) {
    return rows.reduce( (acc, row) => {
        keys.reduce( (parent, key, i) =>
            parent[row[key]] = parent[row[key]] || (i === keys.length - 1 ? [row] : {})
        , acc);
        return acc;
    }, {});
}

const data = [{ID: 1,Main: "Financial",Sub: "Forecasts",Detail: "General"}, {ID: 2,Main: "Financial",Sub: "HR", Detail: "Headcount" }];
const result1 = tree(data, ["Main", "Sub", "Detail"]);
console.log(result1); 

请注意,传播语法会产生浅表副本。相反,在此解决方案中,累加器被传递到内部reduce。因此,我们实际上将新行的分层数据合并到了现场的累加器中。

答案 2 :(得分:1)

您可以迭代键并为最后一个键获取一个对象(而不是最后一个键)或将数组作为最后一个键,然后将数据推入数组。

const tree = (rowsArray, keysArray) => {
    return rowsArray.reduce((acc, row) => {
        keysArray
            .map(k => row[k])
            .reduce((o, k, i, { length }) => o[k] = o[k] || (i + 1 === length ? []: {}), acc)
            .push(row);
        return acc;
    }, {});
}

const data = [{ ID: 1, Main: "Financial", Sub: "Forecasts", Detail: "General" }, { ID: 2, Main: "Financial", Sub: "HR", Detail: "Headcount" }];

const result1 = tree(data, ["Main", "Sub", "Detail"]);
console.log(result1); 
.as-console-wrapper { max-height: 100% !important; top: 0; }

答案 3 :(得分:1)

您可以遍历data并根据提供的keys创建一个唯一密钥,然后通过深度克隆递归地生成输出结构。

const data = [{
        ID: 1,
        Main: "Financial",
        Sub: "Forecasts",
        Detail: "General"
    }, {
        ID: 2,
        Main: "Financial",
        Sub: "HR",
        Detail: "Headcount"
}];

function generateKey(keys,json){
   return keys.reduce(function(o,i){
      o += json[i] + "_";
      return o;
   },'');
}

function merge(first,second){
 for(var i in second){
   if(!first.hasOwnProperty(i)){
      first[i] = second[i];
   }else{
      first[i] = merge(first[i],second[i]);
   }
 }
 return first;
}

function generateTree(input,keys){
  let values = input.reduce(function(o,i){
      var key = generateKey(keys,i);
      if(!o.hasOwnProperty(key)){
         o[key] = [];
      }
      o[key].push(i);
      return o;
  },{});

  return Object.keys(values).reduce(function(o,i){
     var valueKeys = i.split('_');
     var oo = {};
     for(var index = valueKeys.length -2; index >=0 ;index--){
        var out = {};
        if(index === valueKeys.length -2){
           out[valueKeys[index]] = values[i];
        }else{
           out[valueKeys[index]] = oo;
        }
        oo = out;
     }
     o = merge(o,oo);
     return o;
  },{});
}

console.log(generateTree(data,["Main", "Sub", "Detail"])); 

jsFiddle演示-https://jsfiddle.net/6jots8Lc/