将带有项目的路径转换为树对象

时间:2019-10-24 09:29:58

标签: javascript node.js json ramda.js

我试图将包含带有项目的路径的对象数组转换为数据树,所以我在路径上写了一个函数路径循环:

从此数组:

  
[
  { userName: "1", tags: ["A;B"] },
  { userName: "2", tags: ["A;B"] },
  { userName: "3", tags: ["A;"] },
  { userName: "4", tags: ["A;B;C"] },
  { userName: "5", tags: ["A;B"] },
  { userName: "6", tags: ["A;B;C;D"] } 
]

此结构:

  
[{
    name: "A",
    families: [{
        name: "B",
        families: [{
            name: "C",
            families: [{
                name: "D",
                families: [],
                items: ["6"]
            }],
            items: ["4"]
        }],
        items: ["1", "2", "5"]
    }],
    items: ["3"]
}]
function convertListToTree(associationList) {
    let tree = [];
    for (let i = 0; i < associationList.length; i++) {
        let path = associationList[i].tags[0].split(';');
        let assetName = associationList[i].userName;
        let currentLevel = tree;
        for (let j = 0; j < path.length; j++) {
            let familyName = path[j];
            let existingPath = findWhere(currentLevel, 'name', familyName);
            if (existingPath) {
                if (j === path.length - 1) {
                    existingPath.items.push(assetName);
                }
                currentLevel = existingPath.families;
            } else {
                let assets = [];
                if (j === path.length - 1) {
                    assets.push(assetName)
                }
                let newPart = {
                    name: familyName,
                    families: [],
                    items: assets,
                };
                currentLevel.push(newPart);
                currentLevel = newPart.families;
            }
        }
    }
    return tree;
}

function findWhere(array, key, value) {
    let t = 0;
    while (t < array.length && array[t][key] !== value) {
        t++;
    }
    if (t < array.length) {
        return array[t]
    } else {
        return false;
    }
}

但是我这里有些问题,期望的输出与我想要的不一样

[
  {
    "name": "A",
    "families": [
      {
        "name": "B",
        "families": [
          {
            "name": "C",
            "families": [
              {
                "name": "D",
                "families": [],
                "items": [
                  "6"
                ]
              }
            ],
            "items": [
              "4"
            ]
          }
        ],
        "items": [
          "1",
          "2",
          "5"
        ]
      },
      {
        "name": "",
        "families": [],
        "items": [
          "3"
        ]
      }
    ],
    "items": []
  }
]

有人可以帮我解决这个问题

3 个答案:

答案 0 :(得分:1)

您应该能够使用递归来实现此目的,方法是在每个级别调用getFamilies和getUsers函数:

const allTags = ["A", "B", "C", "D"];

let a = [ { "userName": "1", "tags": ["A;B"] }, { "userName": "2", "tags": ["A;B"] }, { "userName": "3", "tags": ["A;"] }, { "userName": "4", "tags": ["A;B;C"] }, { "userName": "5", "tags": ["A;B"] }, { "userName": "6", "tags": ["A;B;C;D"] } ];

// This function assumes order is not important, if it is, remove the sort() calls.
function arraysEqual(a1, a2) {
    return a1.length === a2.length && a1.sort().every(function(value, index) { return value === a2.sort()[index]});
}

function getUserNames(tags, arr) {
    return arr.filter(v => arraysEqual(v.tags[0].split(';').filter(a => a),tags)).map(({userName}) => userName);
}

function getFamilies(tags) {
    if (tags.length >= allTags.length) return [];
    const name = allTags[tags.length];
    const path = [...tags, name];
    return [{ name, families: getFamilies(path), items: getUserNames(path, a)}];
}

let res = getFamilies([]);
console.log('Result:', JSON.stringify(res, null, 4));

答案 1 :(得分:1)

这里的想法是迭代数据(reduce循环),每当Map中缺少某个节点时(nodesMap),请使用createBranch递归创建该节点,然后创建父节点( (如果需要...),然后将该节点分配给父节点,依此类推。最后一步是获取唯一的根路径列表(数据中的A),并将其从Map(tree)提取到数组中。

const createBranch = ([name, ...tagsList], nodesMap, node) => {
  if(!nodesMap.has(name)) { // create node if not in the Map
    const node = { name, families: [], items: [] };
    
    nodesMap.set(name, node);
    
    // if not root of branch create the parent...
    if(tagsList.length) createBranch(tagsList, nodesMap, node);
  };

  // if a parent assign the child to the parent's families
  if(node) nodesMap.get(name).families.push(node);
};

const createTree = data => { 
  const tree = data.reduce((nodesMap, { userName: item, tags: [tags] }) => {
    const tagsList = tags.match(/[^;]+/g).reverse(); // get all nodes in branch and reverse
    const name = tagsList[0]; // get the leaf
    
    if(!nodesMap.has(name)) createBranch(tagsList, nodesMap); // if the leaf doesn't exist create the entire branch
    
    nodesMap.get(name).items.push(item); // assign the item to the leaf's items
  
    return nodesMap;
  }, new Map());
  
    // get a list of uniqnue roots 
  const roots = [...new Set(data.map(({ tags: [tags] }) => tags.split(';')[0]))];
  
  return roots.map(root => tree.get(root)); // get an array of root nodes
}

const data = [{"userName":"1","tags":["A;B"]},{"userName":"2","tags":["A;B"]},{"userName":"3","tags":["A;"]},{"userName":"4","tags":["A;B;C"]},{"userName":"5","tags":["A;B"]},{"userName":"6","tags":["A;B;C;D"]}];

const result = createTree(data);

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

答案 2 :(得分:1)

允许我进行两个小更改,而ramda的mergeDeepWithKey将为您完成大部分工作。


在开始之前进行更改:

  • tags设为一个数组,而不是包含一个字符串的数组(即tags[0].split(";")
  • 允许系列成为类似于字典的对象而不是数组(如果需要数组格式,则为Object.values(dict)

解决方案:

  1. 使用reduce
  2. 将每个条目转换为所需格式的路径
  3. 使用自定义逻辑合并所有路径:
    • 合并name条目时,请勿更改name
    • 合并items个条目时,请串联

const inp = [
  { userName: "1", tags: ["A","B"] },
  { userName: "2", tags: ["A","B"] },
  { userName: "3", tags: ["A"] },
  { userName: "4", tags: ["A","B","C"] },
  { userName: "5", tags: ["A","B"] },
  { userName: "6", tags: ["A","B","C","D"] } 
];

// Transform an input element to a nested path of the right format
const Path = ({ userName, tags }) => tags
  .slice(0, -1)
  .reduceRight(
    (families, name) => ({ name, families: { [families.name]: families },
      items: []
    }),
    ({ name: last(tags), families: {}, items: [userName] })
  );
    
// When merging path entries, use this custom logic
const mergePathEntry = (k, v1, v2) => 
  k === "name" ? v1 :
  k === "items" ? v1.concat(v2) :
  null;


const result = inp
  .map(Path)
  // Watch out for inp.length < 2
  .reduce(
    mergeDeepWithKey(mergePathEntry)
  )

console.log(JSON.stringify(result, null, 2));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const { mergeDeepWithKey, last } = R;</script>

相关问题