JavaScript - рекурсивно построить древовидную структуру данных - PullRequest
0 голосов
/ 11 ноября 2018

У меня есть функция под названием tree, которая принимает массив объектов (как поля данных из базы данных) и массив строк для ключей. Функция перебирает rowArray и рекурсивно создает объект с вложенными свойствами на основе 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 голосов
/ 11 ноября 2018

Вы можете перебрать 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 Demo - https://jsfiddle.net/6jots8Lc/

0 голосов
/ 11 ноября 2018

Вы можете сделать это так:

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. И поэтому мы фактически объединяем иерархические данные новой строки в накопитель на месте.

0 голосов
/ 11 ноября 2018

Вы можете перебрать ключи и взять либо объект для не последнего ключа, либо массив для последнего ключа и затем передать данные в массив.

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; }
0 голосов
/ 11 ноября 2018

Проблема в том, что ваша функция слияния не глубокая . Когда вы присваиваете значения аккумулятору, вы перезаписываете существующие свойства - в этом случае Financial.

Я включил функцию глубокого слияния отсюда и теперь она работает.

Я также исправил некоторые ссылочные ошибки, которые у вас были:

  • 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...