简体   繁体   English

从 Typescript / JavaScript 中的平面数组构建树数组

[英]Build tree array from flat array in Typescript / JavaScript

I have a complex json file that I have to handle with TypeScript / Javascript to make it hierarchical, in order to later build a questionnaire.我有一个复杂的 json 文件,我必须用 TypeScript / Javascript 处理它以使其分层,以便以后构建问卷。 Every entry of the json has a Id (unique), ParentId (0 If root), Text, Description. json 的每个条目都有一个 Id(唯一)、ParentId(0 如果是 root)、Text、Description。

My Typescript Interface我的Typescript接口

export interface Question {
    Id: number;
    Text: string;
    Desc: string;
    ParentId: number;
    ChildAnswers?: Answer[];
}

export interface Answer {
    Id: number;
    Text: string;
    Desc: string;
    ParentId: number;
    ChildQuestion?: Question;
}

I can guarantee that when the object is an answer it will only have one child which we can assume to be a question.我可以保证当 object 是一个答案时,它只会有一个孩子,我们可以假设它是一个问题。

Flat Data Example:平面数据示例:

[
{
    Id: 1,
    Text: 'What kind of apple is it?',
    Desc: '',
    ParentId: 0
},
{
    Id: 2,
    Text: 'Green Apple',
    Desc: '',
    ParentId: 1
},
{
    Id: 3,
    Text: 'Red Apple',
    Desc: '',
    ParentId: 1
},
{
    Id: 4,
    Text: 'Purple GMO Apple',
    Desc: '',
    ParentId: 1
},
{
    Id: 5,
    Text: 'What is the issue with the apple?',
    Desc: '',
    ParentId: 2
},
{
    Id: 6,
    Text: 'Spoiled.',
    Desc: '',
    ParentId: 5
},
{
    Id: 7,
    Text: 'Taste Bad.',
    Desc: '',
    ParentId: 5
},
{
    Id: 8,
    Text: 'Too Ripe.',
    Desc: '',
    ParentId: 5
},
{
    Id: 9,
    Text: 'Is not an apple.',
    Desc: '',
    ParentId: 5
},
{
    Id: 10,
    Text: 'The apple was not green.',
    Desc: '',
    ParentId: 5
},
... So on ...
]

My Goal我的目标

{
    Id: 1,
    Text: 'What kind of apple is it?',
    Desc: '',
    ParentId: 0,
    ChildAnswers: [
        {
            Id: 2,
            Text: 'Green Apple',
            Desc: '',
            ParentId: 1,
            ChildQuestion: {
                Id: 5,
                Text: 'What is the issue with the apple?',
                Desc: '',
                ParentId: 2,
                ChildAnswers: [
                    {
                        Id: 6,
                        Text: 'Spoiled.',
                        Desc: '',
                        ParentId: 5,
                        ... So on ...
                    },
                    {
                        Id: 7,
                        Text: 'Taste Bad.',
                        Desc: '',
                        ParentId: 5,
                        ... So on ...
                    },
                    {
                        Id: 8,
                        Text: 'Too Ripe.',
                        Desc: '',
                        ParentId: 5,
                        ... So on ...
                    },
                    {
                        Id: 9,
                        Text: 'Is not an apple.',
                        Desc: '',
                        ParentId: 5,
                        ... So on ...
                    },
                    {
                        Id: 10,
                        Text: 'The apple was not green.',
                        Desc: '',
                        ParentId: 5,
                        ... So on ...
                    },
                    ... So on ...
                ]
            }
        },
        {
            Id: 3,
            Text: 'Red Apple',
            Desc: '',
            ParentId: 1,
            ... So on ...
        },
        {
            Id: 4,
            Text: 'Red Apple',
            Desc: '',
            ParentId: 1,
            ... So on ...
        }
        ... So on ...
    ]
}

I'm currently using this list_to_tree function I found here on stackoverflow, I just don't know if how to tell a question and answer apart.我目前正在使用我在 stackoverflow 上找到的这个 list_to_tree function,我只是不知道如何区分问题和答案。 Should I just check to see if the length is one for a question or at odd intervals mark it?:我应该只检查长度是一个问题还是以奇数间隔标记它?:

function list_to_tree(list) {
    var map = {}, node, roots = [], i;
    for (i = 0; i < list.length; i += 1) {
        map[list[i].Id] = i; // initialize the map
        list[i].Children = []; // initialize the children
    }
    for (i = 0; i < list.length; i += 1) {
        node = list[i];
        if (node.ParentId !== 0) {
            // if you have dangling branches check that map[node.ParentId] exists
            list[map[node.ParentId]].Children.push(node);
        } else {
            roots.push(node);
        }
    }
    return roots;
}

Here is a brute force solution to the problem:这是该问题的蛮力解决方案:

var flat = [
{
    Id: 1,
    Text: 'What kind of apple is it?',
    Desc: '',
    ParentId: 0
},
{
    Id: 2,
    Text: 'Green Apple',
    Desc: '',
    ParentId: 1
},
{
    Id: 3,
    Text: 'Red Apple',
    Desc: '',
    ParentId: 1
},
{
    Id: 4,
    Text: 'Purple GMO Apple',
    Desc: '',
    ParentId: 1
},
{
    Id: 5,
    Text: 'What is the issue with the apple?',
    Desc: '',
    ParentId: 2
},
{
    Id: 6,
    Text: 'Spoiled.',
    Desc: '',
    ParentId: 5
},
{
    Id: 7,
    Text: 'Taste Bad.',
    Desc: '',
    ParentId: 5
},
{
    Id: 8,
    Text: 'Too Ripe.',
    Desc: '',
    ParentId: 5
},
{
    Id: 9,
    Text: 'Is not an apple.',
    Desc: '',
    ParentId: 5
},
{
    Id: 10,
    Text: 'The apple was not green.',
    Desc: '',
    ParentId: 5
},
]

// first get the roots
const tree = flat.filter((question) => question.ParentId === 0);

// Next we are going to call alternating methods recursively.
function populateQuestionChildren(node) {
  const { Id } = node;
  flat.forEach((answer) => {
    if (answer.ParentId === Id) {
      if (!node.ChildAnswers) {
        node.ChildAnswers = [];
      }
      node.ChildAnswers.push(answer);
      populateAnswerChildren(answer);
    }
  });
}

function populateAnswerChildren(node) {
  const { Id } = node;
  flat.forEach((question) => {
    if (question.ParentId === Id) {
      if (!node.ChildQuestions) {
        node.ChildQuestions = [];
      }
      node.ChildQuestions.push(question);
      populateQuestionChildren(question);
    }
  });
}

// Kick off the build for each question tree. 
tree.forEach((question) => {
  populateQuestionChildren(question);
});

It is likely that there are more elegant solutions - but given that this will be only few dozen or a few hundred question/answers - this should get you what you need.可能有更优雅的解决方案 - 但鉴于这将只有几十或几百个问题/答案 - 这应该可以满足您的需求。

[EDIT] [编辑]

I used your interfaces and discovered a problem with my code.我使用了你的接口,发现我的代码有问题。 There is only one "ChildQuestion" on an Answer object.答案 object 上只有一个“ChildQuestion”。 So here is my change to TypeScript to make it work properly.所以这是我对 TypeScript 的更改,以使其正常工作。 I hope it helps:我希望它有帮助:


interface Question {
  Id: number;
  Text: string;
  Desc: string;
  ParentId: number;
  ChildAnswers ? : Answer[];
}

interface Answer {
  Id: number;
  Text: string;
  Desc: string;
  ParentId: number;
  ChildQuestion ? : Question;
}

// first get the roots
const tree = flat.filter((question) => question.ParentId === 0);

function populateQuestionChildren(node: Question) {
  const { Id } = node;
  flat.forEach((answer) => {
    if (answer.ParentId === Id) {
      if (!node.ChildAnswers) {
        node.ChildAnswers = [];
      }
      node.ChildAnswers.push(answer);
      populateAnswerChild(answer);
    }
  });
}

function populateAnswerChild(answer: Answer) {
  const { Id } = answer;
  // switch to every so we can break early once a question is found.
  flat.every((node) => {
    if (node.ParentId === Id) {
      answer.ChildQuestion = node;
      populateQuestionChildren(node);
      return false;
    }
    return true;
  });
}

tree.forEach((question) => {
  populateQuestionChildren(question);
});

I've created an answer based on @nephiw's answer.我根据@nephiw 的回答创建了一个答案。 Since the key will always be Questions or Answers, odd number will always be Answers and even number will be Questions.由于键始终是问题或答案,因此奇数将始终是答案,偶数将始终是问题。 You can simplify into one function instead of two.您可以简化为一个 function 而不是两个。

const items = [
  {
    Id: 1,
    Text: "What kind of apple is it?",
    Desc: "",
    ParentId: 0
  },
  {
    Id: 2,
    Text: "Green Apple",
    Desc: "",
    ParentId: 1
  },
  {
    Id: 3,
    Text: "Red Apple",
    Desc: "",
    ParentId: 1
  },
  {
    Id: 4,
    Text: "Purple GMO Apple",
    Desc: "",
    ParentId: 1
  },
  {
    Id: 5,
    Text: "What is the issue with the apple?",
    Desc: "",
    ParentId: 2
  },
  {
    Id: 6,
    Text: "Spoiled.",
    Desc: "",
    ParentId: 5
  },
  {
    Id: 7,
    Text: "Taste Bad.",
    Desc: "",
    ParentId: 5
  },
  {
    Id: 8,
    Text: "Too Ripe.",
    Desc: "",
    ParentId: 5
  },
  {
    Id: 9,
    Text: "Is not an apple.",
    Desc: "",
    ParentId: 5
  },
  {
    Id: 10,
    Text: "The apple was not green.",
    Desc: "",
    ParentId: 5
  }
];

const root = items.filter(item => item.ParentId === 0);
const populateChildren = (curentItem, nested) => {
  const { Id } = curentItem;
  const key = nested % 2 === 1 ? 'ChildAnswers' : 'ChildQuestions';
  items.forEach((item) => {
    if (item.ParentId === Id) {
      if (!curentItem[key]) {
        curentItem[key] = [];
      }
      curentItem[key].push(item);
      populateChildren(item, nested + 1);
    }
  });
}
root.forEach((item) => { 
  populateChildren(item, 1);
});
console.log(root);

You could take an approach where you collect the parts independently of the order of the given data and build a tree and map the children by toggeling the question/answer scheme.您可以采取一种方法,独立于给定数据的顺序收集零件,并通过切换问答方案来构建树和 map 孩子。

 var data = [{ Id: 1, Text: 'What kind of apple is it?', Desc: '', ParentId: 0 }, { Id: 2, Text: 'Green Apple', Desc: '', ParentId: 1 }, { Id: 3, Text: 'Red Apple', Desc: '', ParentId: 1 }, { Id: 4, Text: 'Purple GMO Apple', Desc: '', ParentId: 1 }, { Id: 5, Text: 'What is the issue with the apple?', Desc: '', ParentId: 2 }, { Id: 6, Text: 'Spoiled.', Desc: '', ParentId: 5 }, { Id: 7, Text: 'Taste Bad.', Desc: '', ParentId: 5 }, { Id: 8, Text: 'Too Ripe.', Desc: '', ParentId: 5 }, { Id: 9, Text: 'Is not an apple.', Desc: '', ParentId: 5 }, { Id: 10, Text: 'The apple was not green.', Desc: '', ParentId: 5 }], tree = function (data, root) { const next = { ChildAnswers: 'ChildQuestion', ChildQuestion: 'ChildAnswers' }, toggle = type => ({ children, ...o }) => Object.assign(o, children && { [type]: children.map(toggle(next[type])) }), t = {}; data.forEach(o => { Object.assign(t[o.Id] = t[o.Id] || {}, o); t[o.ParentId] = t[o.ParentId] || {}; t[o.ParentId].children = t[o.ParentId].children || []; t[o.ParentId].children.push(t[o.Id]); }); return t[root].children.map(toggle('ChildAnswers')); }(data, 0); console.log(tree);
 .as-console-wrapper { max-height: 100%;important: top; 0; }

my code:我的代码:

  makeTree(nodes: any[], parentId: any): any {
return nodes
  .filter((node) => node.parentId === parentId)
  .reduce(
    (tree, node) => [
      ...tree,
      {
        ...node,
        children: this.makeTree(nodes, node.id),
      },
    ],
    []
  ); }

Generate Node from Array从数组生成节点

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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