Преобразование списка в дерево по потомкам и предкам

#javascript #typescript #recursion #tree #traversal

Вопрос:

Я пытаюсь преобразовать список в дерево, используя параметры потомков и предков в элементе:

У меня, например, есть что-то вроде этого:

 const itemToUpdate = {
  id: 1,
  ancestors: [
    {
      id: 2,
      ancestors: [
        {
          id: 4
        }
      ]
    }
  ],
  descendants: [
    {
      id: 3,
      descendants: [
        {
          id: 5
        },
        {
          id: 6,
          descendants: [
            {
              id: 8
            },
            {
              id: 9
            }
          ]
        },
        {
          id: 7
        }
      ]
    }
  ]
};

const updatedItem = {
  id: 4,
  children: [
    {
      id: 2,
      children: [
        {
          id: 1,
          children: [
            {
              id: 3,
              children: [
                {
                  id: 5
                },
                {
                  id: 6,
                  children: [
                    {
                      id: 8
                    },
                    {
                      id: 9
                    }
                  ]
                },
                {
                  id: 7
                }
              ]
            }
          ]
        }
      ]
    }
  ]
};
 

Дело в том, что деревья могут быть вложены на многих уровнях. Кроме того, на уровне может быть много предков и потомков, поэтому мне нужно более общее решение. Дерево должно быть создано вверх от предков и вниз от потомков.

Я нашел способ получить потомков дерева, но мне все равно нужно добавить к этому предков на вершине этого дерева:

 export const convertListToTree = <T extends ApiTree<T>>(list: Array<T>, key = 'ascendants'): Array<T> => {
  const getNestedItem = (path: Array<string>, tree: Array<T>): T => {
    const nestedItem = tree.find(nestedItem => nestedItem.id === path[0]);
    const newPath = path.slice(1);
    if (!newPath.length) {
      return nestedItem;
    } else if (nestedItem?.[key]) {
      return getNestedItem(newPath, nestedItem?.[key]);
    } else {
      return getNestedItem(newPath, tree);
    }
  };

  const sortListByPathLength = (list: Array<T>): Array<T> => {
    return sortBy(list, (item: T) => item.path?.length);
  };

  const addRootItem = (item: T, tree: Array<T>): void => {
    tree.push(item);
  };

  const addChildrenItem = (item: T, tree: Array<T>): void => {
    const parent = getNestedItem(item.path, tree);
    if (parent) {
      parent[key].push(item);
    } else {
      addRootItem(item, tree);
    }
  };

  const tree = [];
  if (list?.length) {
    const sortedList = sortListByPathLength(list);
    sortedList.forEach(item => {
      if (!item.path?.length) {
        addRootItem(item, tree);
      } else {
        addChildrenItem(item, tree);
      }
    });
  }
  return tree;
};
 

Любые советы будут более чем приветствоваться. 🙏

Комментарии:

1. что делать, если у вас есть более одного предмета? что вы пробовали?

2. извините, что должно произойти, если у узла более одного предка? это действительно важно для решения.

3. @NinaScholz Хм, это хороший вопрос. Я думаю, что мы должны переместить один или несколько корневых уровней ниже, как в этой статье: shareablecode.com/snippets/…

4. Мне не ясно, что должно произойти с несколькими предками? Что тогда было бы корнем дерева? В моем понимании в этом случае нет дерева, а есть направленный ациклический граф. Пожалуйста, отредактируйте свой вопрос и приведите пример такого ввода с ожидаемым результатом.

5. @NinaScholz Ты совершенно прав, извини.. У каждого узла может быть несколько потомков, но только один предок. Я обновил пример с этой ситуацией. Извините за путаницу

Ответ №1:

Предполагая, что для каждого уровня существует только один предок, вы можете использовать рекурсивную функцию, которая сохраняет потомков, но возвращает самый ранний узел предка.

 const
    fn = ({ id, ancestors, descendants }, children) => {
        const node = { id };
        if (Array.isArray(children)) node.children = children;
        if (descendants) node.children = descendants.map(fn);
        return ancestors
            ? fn(ancestors[0], [node])
            : node;
    },
    data = { id: 1, ancestors: [{ id: 2, ancestors: [{ id: 4 }] }], descendants: [{ id: 3, descendants: [{ id: 5 }] }] },
    result = fn(data);

console.log(result); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

Комментарии:

1. Ух ты, круто. Мы должны предположить, что на каждом уровне может быть несколько потомков и предков. Но это уже кое-что 🙌

Ответ №2:

Я не совсем уверен, чего вы хотите там достичь. Не могли бы вы подробнее остановиться на этом?
Вот как вы бы преобразовали свой конкретный пример, если это поможет.

 function convert(item) {
  return {
    id: item.ancestors[0].id,
    children: [
      {
        id: item.id,
        children: [
          {
            id: item.descendants[0].id
          }
        ]
      }      
    ]
  }
}
 

Комментарии:

1. Дело в том, что деревья могут быть вложены на многих уровнях. Кроме того, на уровне может быть много предков и потомков, поэтому мне нужно более общее решение. Дерево должно быть создано вверх от предков и вниз от потомков.

2. @Patryk, вы могли бы добавить этот случай к вопросу.

3. @Patryk не могли бы вы привести пример с большим количеством элементов в массивах, я не уверен, как вы хотите обрабатывать назначение идентификаторов, которые могут быть достаточно сложными