자바 스크립트의 플랫 배열에서 트리 배열 빌드


134

나중에 트리를 만들기 위해 계층 구조로 만들기 위해 자바 스크립트로 처리해야하는 복잡한 json 파일이 있습니다. json의 모든 항목에는 다음이 있습니다.

json 데이터는 이미 "정렬"되었습니다. 내 말은 항목이 상위 노드 또는 형제 노드 위에 있고 하위 노드 또는 형제 노드 아래에 있음을 의미합니다.

입력 :

{
    "People": [
        {
            "id": "12",
            "parentId": "0",
            "text": "Man",
            "level": "1",
            "children": null
        },
        {
            "id": "6",
            "parentId": "12",
            "text": "Boy",
            "level": "2",
            "children": null
        },
                {
            "id": "7",
            "parentId": "12",
            "text": "Other",
            "level": "2",
            "children": null
        },
        {
            "id": "9",
            "parentId": "0",
            "text": "Woman",
            "level": "1",
            "children": null
        },
        {
            "id": "11",
            "parentId": "9",
            "text": "Girl",
            "level": "2",
            "children": null
        }
    ],
    "Animals": [
        {
            "id": "5",
            "parentId": "0",
            "text": "Dog",
            "level": "1",
            "children": null
        },
        {
            "id": "8",
            "parentId": "5",
            "text": "Puppy",
            "level": "2",
            "children": null
        },
        {
            "id": "10",
            "parentId": "13",
            "text": "Cat",
            "level": "1",
            "children": null
        },
        {
            "id": "14",
            "parentId": "13",
            "text": "Kitten",
            "level": "2",
            "children": null
        },
    ]
}

예상 출력 :

{
    "People": [
        {
            "id": "12",
            "parentId": "0",
            "text": "Man",
            "level": "1",
            "children": [
                {
                    "id": "6",
                    "parentId": "12",
                    "text": "Boy",
                    "level": "2",
                    "children": null
                },
                {
                    "id": "7",
                    "parentId": "12",
                    "text": "Other",
                    "level": "2",
                    "children": null
                }   
            ]
        },
        {
            "id": "9",
            "parentId": "0",
            "text": "Woman",
            "level": "1",
            "children":
            {

                "id": "11",
                "parentId": "9",
                "text": "Girl",
                "level": "2",
                "children": null
            }
        }

    ],    

    "Animals": [
        {
            "id": "5",
            "parentId": "0",
            "text": "Dog",
            "level": "1",
            "children": 
                {
                    "id": "8",
                    "parentId": "5",
                    "text": "Puppy",
                    "level": "2",
                    "children": null
                }
        },
        {
            "id": "10",
            "parentId": "13",
            "text": "Cat",
            "level": "1",
            "children": 
            {
                "id": "14",
                "parentId": "13",
                "text": "Kitten",
                "level": "2",
                "children": null
            }
        }

    ]
}

2
여러 가지 방법이 있습니다. 아직 시도해 보셨습니까?
bfavaretto 2013-08-02

parentIdof 0는 부모 ID가 없으며 최상위 레이어 여야 함 을 의미 한다고 가정합니다 .
Donnie D' Amato 2011

일반적으로 이러한 종류의 작업에는 광범위한 작업 지식 개체가 필요했습니다. 좋은 질문
강가 다르 잔누

답변:


156

지도 조회를 사용하는 경우 효율적인 솔루션이 있습니다. 부모가 항상 자녀보다 먼저 오는 경우 두 개의 for 루프를 병합 할 수 있습니다. 여러 뿌리를 지원합니다. 매달린 가지에 오류가 발생하지만 무시하도록 수정할 수 있습니다. 타사 라이브러리가 필요하지 않습니다. 내가 말할 수있는 한 가장 빠른 솔루션입니다.

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;
}

var entries = [{
    "id": "12",
    "parentId": "0",
    "text": "Man",
    "level": "1",
    "children": null
  },
  {
    "id": "6",
    "parentId": "12",
    "text": "Boy",
    "level": "2",
    "children": null
  },
  {
    "id": "7",
    "parentId": "12",
    "text": "Other",
    "level": "2",
    "children": null
  },
  {
    "id": "9",
    "parentId": "0",
    "text": "Woman",
    "level": "1",
    "children": null
  },
  {
    "id": "11",
    "parentId": "9",
    "text": "Girl",
    "level": "2",
    "children": null
  }
];

console.log(list_to_tree(entries));

복잡성 이론에 따르면이 솔루션은 Θ (n log (n))입니다. 재귀 필터 솔루션은 Θ (n ^ 2)이며 큰 데이터 세트의 경우 문제가 될 수 있습니다.


28
이 솔루션을 사용하면 부모가 먼저 맵에 푸시되도록 노드를 구체적으로 정렬해야합니다. 그렇지 않으면 조회 프로세스에서 오류가 발생하므로 레벨 속성에서 정렬해야합니다. 먼저지도에 밀어 넣습니다. 조회를 위해 별도의 for 루프를 사용하십시오. (나는 정렬을 선호하지만 레벨 속성이 없을 때 별도의 루프가 옵션 일 수 있습니다.)
Sander

처음에는 추가 정보 (예 : 배열이 후속 조상 인 [1, 5, 6])와 같은 경로가 효율적으로 사용될 수 없다는 것이 놀랍습니다. 하지만 코드를 보면 O (n)라고 믿기 때문에
감각이 생깁니다

1
좋은 대답에도 불구하고 복잡합니다. 두 개의 라인 코드에만 내 대답을 적용하십시오. link
Iman Bahrampour 2017-08-26

이 솔루션이 왜 Θ (n log (n))인지 설명해 주시겠습니까? O (n) 시간이 걸리는 것 같습니다.
amrender 싱

for 루프 내부의 @amrendersingh은 map(이론적으로) O (log n) 인 해시 조회입니다 .
할시 온

72

@Sander에서 언급했듯이 @Halcyon의 대답 은 사전 정렬 된 배열을 가정하지만 다음은 그렇지 않습니다. (하지만 underscore.js를로드했다고 가정합니다. 바닐라 자바 ​​스크립트로 작성할 수 있습니다.)

암호

// Example usage
var arr = [
    {'id':1 ,'parentid' : 0},
    {'id':2 ,'parentid' : 1},
    {'id':3 ,'parentid' : 1},
    {'id':4 ,'parentid' : 2},
    {'id':5 ,'parentid' : 0},
    {'id':6 ,'parentid' : 0},
    {'id':7 ,'parentid' : 4}
];

unflatten = function( array, parent, tree ){
    tree = typeof tree !== 'undefined' ? tree : [];
    parent = typeof parent !== 'undefined' ? parent : { id: 0 };
        
    var children = _.filter( array, function(child){ return child.parentid == parent.id; });
    
    if( !_.isEmpty( children )  ){
        if( parent.id == 0 ){
           tree = children;   
        }else{
           parent['children'] = children
        }
        _.each( children, function( child ){ unflatten( array, child ) } );                    
    }
    
    return tree;
}

tree = unflatten( arr );
document.body.innerHTML = "<pre>" + (JSON.stringify(tree, null, " "))
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>

요구 사항

'id'와 'parentid'속성이 각각 ID와 부모 ID를 나타내는 것으로 가정합니다. 부모 ID가 0 인 요소가 있어야합니다. 그렇지 않으면 빈 배열이 반환됩니다. 고아 요소와 그 하위 요소가 '손실'됨

http://jsfiddle.net/LkkwH/1/


4
else { parent['children'] = []; }첫 번째 if 절 뒤에 추가 하여 모든 노드에 속성이 있는지 확인할 children수 있습니다 (노드가 리프 노드 인 경우 비어 있음)
Christopher

2
코드 스 니펫이 완벽하게 작동했습니다. 감사합니다 !! 있는 유일한 방법입니다 : tree재귀 적으로 함수를 호출 할 때 내가 라인이 생각하는, 그래서 인수로 전달되지 않습니다 tree = typeof tree !== 'undefined' ? tree : [];에 의해 대체 될 수있다let tree = [];
오스카 칼데론

null0 대신 parent_ids 를 허용하도록 수정할 수 있습니까?편집 : 신경 쓰지 마십시오 . id: 0id: null.
dlinx90

위의 답변은 두 개의 루프를 사용하므로 개선 될 수 있습니다. O (n) 솔루션을 구현하는 npm 모듈을 찾을 수 없었기 때문에 다음 모듈을 만들었습니다 (단위 테스트, 100 % 코드 커버리지, 크기 0.5kb, 입력 포함). 누군가에게 도움이 될 수도 있습니다 : npmjs.com/package/performant-array-to-tree
Philip Stanislaus

4
관심있는 사람이라면 코드를 바닐라 js로 쉽게 변환 할 수 있습니다. jsfiddle.net/LkkwH/853
xec

48

(보너스 1 : 노드 주문 가능 여부)

(보너스 2 : 제 3 자 라이브러리 필요 없음, 일반 JS)

(BONUS3 : 사용자 "Elias Rabl"이 이것이 가장 빠른 솔루션이라고 말합니다. 아래 답변을 참조하십시오.)

여기있어:

const createDataTree = dataset => {
    let hashTable = Object.create(null)
    dataset.forEach( aData => hashTable[aData.ID] = { ...aData, childNodes : [] } )
    let dataTree = []
    dataset.forEach( aData => {
      if( aData.parentID ) hashTable[aData.parentID].childNodes.push(hashTable[aData.ID])
      else dataTree.push(hashTable[aData.ID])
    } )
    return dataTree
}

다음은 솔루션이 작동하는 방식을 이해하는 데 도움이 될 수있는 테스트입니다.

it('creates a correct shape of dataTree', () => {

    let dataSet = [
        {
            "ID": 1,
            "Phone": "(403) 125-2552",
            "City": "Coevorden",
            "Name": "Grady"
        },
        {
            "ID": 2,
            "parentID": 1,
            "Phone": "(979) 486-1932",
            "City": "Chełm",
            "Name": "Scarlet"
        }
    ]

    let expectedDataTree = [ 
    {
            "ID": 1,
            "Phone": "(403) 125-2552",
            "City": "Coevorden",
            "Name": "Grady",
            childNodes : [
                {
                    "ID": 2,
                    "parentID": 1,
                    "Phone": "(979) 486-1932",
                    "City": "Chełm",
                    "Name": "Scarlet",
                    childNodes : []
                }
            ]
    } 
    ]

  expect( createDataTree(dataSet) ).toEqual(expectedDataTree)
});

2
childNodes필요할 때만 추가하면 더 정확하지 않을까요 ? 첫 번째에서 제거하고 forEach두 번째로 이동하여?
arpl

@arpl이 동의했습니다. 필요한 경우 쉽게 변경할 수 있습니다. 또는 기본 방식이어야한다고 생각하면 변경할 수 있습니다.
FurkanO '1911.29

@FurkanO 정말 좋은 솔루션입니다. 그러나 함수형 프로그래밍 (변이 없음)을 사용하여이 성능에 가까운 곳을 얻을 수있을 것입니다
Dac0d3r

34

같은 문제가 있었지만 데이터가 정렬 되었는지 여부를 확신 할 수 없었습니다 . 제 3 자 라이브러리를 사용할 수 없었기 때문에 이것은 단지 바닐라 Js입니다. 입력 데이터는 @Stephen의 예에서 가져올 수 있습니다.

 var arr = [
        {'id':1 ,'parentid' : 0},
        {'id':4 ,'parentid' : 2},
        {'id':3 ,'parentid' : 1},
        {'id':5 ,'parentid' : 0},
        {'id':6 ,'parentid' : 0},
        {'id':2 ,'parentid' : 1},
        {'id':7 ,'parentid' : 4},
        {'id':8 ,'parentid' : 1}
      ];
    function unflatten(arr) {
      var tree = [],
          mappedArr = {},
          arrElem,
          mappedElem;

      // First map the nodes of the array to an object -> create a hash table.
      for(var i = 0, len = arr.length; i < len; i++) {
        arrElem = arr[i];
        mappedArr[arrElem.id] = arrElem;
        mappedArr[arrElem.id]['children'] = [];
      }


      for (var id in mappedArr) {
        if (mappedArr.hasOwnProperty(id)) {
          mappedElem = mappedArr[id];
          // If the element is not at the root level, add it to its parent array of children.
          if (mappedElem.parentid) {
            mappedArr[mappedElem['parentid']]['children'].push(mappedElem);
          }
          // If the element is at the root level, add it to first level elements array.
          else {
            tree.push(mappedElem);
          }
        }
      }
      return tree;
    }

var tree = unflatten(arr);
document.body.innerHTML = "<pre>" + (JSON.stringify(tree, null, " "))

JS 바이올린

플랫 배열에서 트리로


어떤 경우에는 정의되지 않은 mappedArr[mappedElem['parentid']]['children']액세스 할 수 없기 때문에 실패했습니다 children.
Al-

부모 ID : 1에서 어떻게 시작합니까?
vinni

31

이 ES6 접근 방식을 사용하십시오. 매력처럼 작동

// Data Set
// One top level comment 
const comments = [{
    id: 1,
    parent_id: null
}, {
    id: 2,
    parent_id: 1
}, {
    id: 3,
    parent_id: 1
}, {
    id: 4,
    parent_id: 2
}, {
    id: 5,
    parent_id: 4
}];

const nest = (items, id = null, link = 'parent_id') =>
  items
    .filter(item => item[link] === id)
    .map(item => ({ ...item, children: nest(items, item.id) }));

console.log(
  nest(comments)
)


3
내가 생각하는 가장 짧고 가장 좋은 대답
자바 남자 스크립트

2
FurkanO의 답변에 비해 sloooow
Geza Turi

16

더 간단한 함수 목록-트리 라이트

npm install list-to-tree-lite

listToTree(list)

출처:

function listToTree(data, options) {
    options = options || {};
    var ID_KEY = options.idKey || 'id';
    var PARENT_KEY = options.parentKey || 'parent';
    var CHILDREN_KEY = options.childrenKey || 'children';

    var tree = [],
        childrenOf = {};
    var item, id, parentId;

    for (var i = 0, length = data.length; i < length; i++) {
        item = data[i];
        id = item[ID_KEY];
        parentId = item[PARENT_KEY] || 0;
        // every item may have children
        childrenOf[id] = childrenOf[id] || [];
        // init its children
        item[CHILDREN_KEY] = childrenOf[id];
        if (parentId != 0) {
            // init its parent's children object
            childrenOf[parentId] = childrenOf[parentId] || [];
            // push it into its parent's children object
            childrenOf[parentId].push(item);
        } else {
            tree.push(item);
        }
    };

    return tree;
}

jsfiddle


10

두 줄로 코딩하면이 질문을 처리 할 수 ​​있습니다.

_(flatArray).forEach(f=>
           {f.nodes=_(flatArray).filter(g=>g.parentId==f.id).value();});

var resultArray=_(flatArray).filter(f=>f.parentId==null).value();

온라인 테스트 (생성 된 트리는 브라우저 콘솔 참조)

요구 사항 :

1- lodash 4 설치 (c #의 Linq와 같은 수행 메서드 => 개체 및 컬렉션을 조작하기위한 자바 스크립트 라이브러리) Lodash

2- 아래와 같은 flatArray :

    var flatArray=
    [{
      id:1,parentId:null,text:"parent1",nodes:[]
    }
   ,{
      id:2,parentId:null,text:"parent2",nodes:[]
    }
    ,
    {
      id:3,parentId:1,text:"childId3Parent1",nodes:[]
    }
    ,
    {
      id:4,parentId:1,text:"childId4Parent1",nodes:[]
    }
    ,
    {
      id:5,parentId:2,text:"childId5Parent2",nodes:[]
    }
    ,
    {
      id:6,parentId:2,text:"childId6Parent2",nodes:[]
    }
    ,
    {
      id:7,parentId:3,text:"childId7Parent3",nodes:[]
    }
    ,
    {
      id:8,parentId:5,text:"childId8Parent5",nodes:[]
    }];

Bakhshabadi 님, 감사합니다.

행운을 빕니다


8

패키지 목록-트리 설치에 유용 할 수 있습니다 .

bower install list-to-tree --save

또는

npm install list-to-tree --save

예를 들어 목록이 있습니다.

var list = [
  {
    id: 1,
    parent: 0
  }, {
    id: 2,
    parent: 1
  }, {
    id: 3,
    parent: 1
  }, {
    id: 4,
    parent: 2
  }, {
    id: 5,
    parent: 2
  }, {
    id: 6,
    parent: 0
  }, {
    id: 7,
    parent: 0
  }, {
    id: 8,
    parent: 7
  }, {
    id: 9,
    parent: 8
  }, {
    id: 10,
    parent: 0
  }
];

패키지 목록-트리 사용 :

var ltt = new LTT(list, {
  key_id: 'id',
  key_parent: 'parent'
});
var tree = ltt.GetTree();

결과:

[{
  "id": 1,
  "parent": 0,
  "child": [
    {
      "id": 2,
      "parent": 1,
      "child": [
        {
          "id": 4,
          "parent": 2
        }, {
          "id": 5, "parent": 2
        }
      ]
    },
    {
      "id": 3,
      "parent": 1
    }
  ]
}, {
  "id": 6,
  "parent": 0
}, {
  "id": 7,
  "parent": 0,
  "child": [
    {
      "id": 8,
      "parent": 7,
      "child": [
        {
          "id": 9,
          "parent": 8
        }
      ]
    }
  ]
}, {
  "id": 10,
  "parent": 0
}];

1
참고 것을 링크 전용 답변 낙심, SO 응답 솔루션 (대 아직 시간이 지남에 따라 부패하는 경향 참조의 다른 경유지)에 대한 검색의 엔드 포인트이어야한다. 여기 독립형 개요를 추가 참조로 링크를 유지하는 고려하시기 바랍니다
클레오 파트라

-1이 왜 좋은지 이해가 안되지만 안타깝게도 gitHub 또는 다른 공용 저장소에서 패키지를 찾을 수 없습니다
oriaj

패키지에 관심을 가져 주셔서 감사합니다. 나중에 확장 할 계획입니다. 다음은 저장소에 대한 링크입니다. github.com/DenQ/list-to-tree
DenQ

@oriaj 프로젝트가 혜택을 보게되어 기쁩니다. 몇 가지 아이디어의 계획
DenQ

잘 작동합니다, @DenQ 감사합니다. 그래도 더 많은 테스트 범위가 있었으면 좋겠습니다!
IliasT

3

사용자 shekhardtu가 제안한 가장 일반적인 두 가지 솔루션 (입력을 미리 정렬 할 필요가없고 코드가 타사 라이브러리에 의존하지 않음을 의미 함)의 성능을 평가하기 위해 테스트 스크립트를 작성했습니다 ( 답변 참조 ). 및 FurkanO ( 답변 참조 ).

http://playcode.io/316025?tabs=console&script.js&output

FurkanO의 솔루션이 가장 빠른 것 같습니다.

/*
** performance test for /programming/18017869/build-tree-array-from-flat-array-in-javascript
*/

// Data Set (e.g. nested comments)
var comments = [{
    id: 1,
    parent_id: null
}, {
    id: 2,
    parent_id: 1
}, {
    id: 3,
    parent_id: 4
}, {
    id: 4,
    parent_id: null
}, {
    id: 5,
    parent_id: 4
}];

// add some random entries
let maxParentId = 10000;
for (let i=6; i<=maxParentId; i++)
{
  let randVal = Math.floor((Math.random() * maxParentId) + 1);
  comments.push({
    id: i,
    parent_id: (randVal % 200 === 0 ? null : randVal)
  });
}

// solution from user "shekhardtu" (https://stackoverflow.com/a/55241491/5135171)
const nest = (items, id = null, link = 'parent_id') =>
  items
    .filter(item => item[link] === id)
    .map(item => ({ ...item, children: nest(items, item.id) }));
;

// solution from user "FurkanO" (https://stackoverflow.com/a/40732240/5135171)
const createDataTree = dataset => {
    let hashTable = Object.create(null)
    dataset.forEach( aData => hashTable[aData.id] = { ...aData, children : [] } )
    let dataTree = []
    dataset.forEach( aData => {
      if( aData.parent_id ) hashTable[aData.parent_id].children.push(hashTable[aData.id])
      else dataTree.push(hashTable[aData.id])
    } )
    return dataTree
};


/*
** lets evaluate the timing for both methods
*/
let t0 = performance.now();
let createDataTreeResult = createDataTree(comments);
let t1 = performance.now();
console.log("Call to createDataTree took " + Math.floor(t1 - t0) + " milliseconds.");

t0 = performance.now();
let nestResult = nest(comments);
t1 = performance.now();
console.log("Call to nest took " + Math.floor(t1 - t0) + " milliseconds.");




//console.log(nestResult);
//console.log(createDataTreeResult);

// bad, but simple way of comparing object equality
console.log(JSON.stringify(nestResult)===JSON.stringify(createDataTreeResult));


2

주문하지 않은 상품에 대한 제안입니다. 이 함수는 단일 루프 및 해시 테이블과 함께 작동하며 id. 루트 노드가 발견되면 객체가 결과 배열에 추가됩니다.

function getTree(data, root) {
    var o = {};
    data.forEach(function (a) {
        if (o[a.id] && o[a.id].children) {
            a.children = o[a.id].children;
        }
        o[a.id] = a;
        o[a.parentId] = o[a.parentId] || {};
        o[a.parentId].children = o[a.parentId].children || [];
        o[a.parentId].children.push(a);
    });
    return o[root].children;
}

var data = { People: [{ id: "12", parentId: "0", text: "Man", level: "1", children: null }, { id: "6", parentId: "12", text: "Boy", level: "2", children: null }, { id: "7", parentId: "12", text: "Other", level: "2", children: null }, { id: "9", parentId: "0", text: "Woman", level: "1", children: null }, { id: "11", parentId: "9", text: "Girl", level: "2", children: null }], Animals: [{ id: "5", parentId: "0", text: "Dog", level: "1", children: null }, { id: "8", parentId: "5", text: "Puppy", level: "2", children: null }, { id: "10", parentId: "13", text: "Cat", level: "1", children: null }, { id: "14", parentId: "13", text: "Kitten", level: "2", children: null }] },
    tree = Object.keys(data).reduce(function (r, k) {
        r[k] = getTree(data[k], '0');
        return r;
    }, {});

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


1

lodashjs (v4.x)로도 수행

function buildTree(arr){
  var a=_.keyBy(arr, 'id')
  return _
   .chain(arr)
   .groupBy('parentId')
   .forEach(function(v,k){ 
     k!='0' && (a[k].children=(a[k].children||[]).concat(v));
   })
   .result('0')
   .value();
}

1

@WilliamLeung의 순수 자바 스크립트 솔루션이 마음에 들지만 때때로 객체에 대한 참조를 유지하기 위해 기존 배열을 변경해야합니다.

function listToTree(data, options) {
  options = options || {};
  var ID_KEY = options.idKey || 'id';
  var PARENT_KEY = options.parentKey || 'parent';
  var CHILDREN_KEY = options.childrenKey || 'children';

  var item, id, parentId;
  var map = {};
    for(var i = 0; i < data.length; i++ ) { // make cache
    if(data[i][ID_KEY]){
      map[data[i][ID_KEY]] = data[i];
      data[i][CHILDREN_KEY] = [];
    }
  }
  for (var i = 0; i < data.length; i++) {
    if(data[i][PARENT_KEY]) { // is a child
      if(map[data[i][PARENT_KEY]]) // for dirty data
      {
        map[data[i][PARENT_KEY]][CHILDREN_KEY].push(data[i]); // add child to parent
        data.splice( i, 1 ); // remove from root
        i--; // iterator correction
      } else {
        data[i][PARENT_KEY] = 0; // clean dirty data
      }
    }
  };
  return data;
}

예 : https://jsfiddle.net/kqw1qsf0/17/


1

var data = [{"country":"india","gender":"male","type":"lower","class":"X"},
			{"country":"china","gender":"female","type":"upper"},
			{"country":"india","gender":"female","type":"lower"},
			{"country":"india","gender":"female","type":"upper"}];
var seq = ["country","type","gender","class"];
var treeData = createHieArr(data,seq);
console.log(treeData)
function createHieArr(data,seq){
	var hieObj = createHieobj(data,seq,0),
		hieArr = convertToHieArr(hieObj,"Top Level");
		return [{"name": "Top Level", "parent": "null",
				     "children" : hieArr}]
	function convertToHieArr(eachObj,parent){
		var arr = [];
		for(var i in eachObj){
			arr.push({"name":i,"parent":parent,"children":convertToHieArr(eachObj[i],i)})
		}
		return arr;
	}
	function createHieobj(data,seq,ind){
		var s = seq[ind];
		if(s == undefined){
			return [];
		}
		var childObj = {};
		for(var ele of data){
			if(ele[s] != undefined){
				if(childObj[ele[s]] == undefined){
					childObj[ele[s]] = [];
				}
				childObj[ele[s]].push(ele);
			}
		}
		ind = ind+1;
		for(var ch in childObj){
			childObj[ch] = createHieobj(childObj[ch],seq,ind)
		}
		return childObj;
	}
}


이 함수는 객체 배열의 데이터를 트리 구조로 변환하는 데 사용되며 이는 d3 트리 대화 형 차트에 필요합니다. 40 줄의 코드만으로 출력을 얻을 수있었습니다. 이 함수는 js에서 재귀 기능을 사용하는 효율적인 방식으로 작성했습니다. 귀하의 의견을 알려주십시오. 감사합니다!!!!
karthik reddy

anwser 덕분에 .. 그것은 내 d3 트리 토폴로지에서 완벽하게 작동합니다 .. 이제 노드의 값을 기반으로 노드 색상을 변경해야한다는 요구 사항이 있습니다. 그래서 JSON에 플래그 값을 전달해야합니다. . 어떻게하나요 .. { "name": "Top Level", "flag": 1, "parent": "null", "children": [{ "name": "india", "flag": 0 "부모": "최고 수준", "어린이"[
Puneeth 쿠마

1

npm 패키지 tree-util을 볼 수 있습니다. . SQL DB 데이터 테이블에서 데이터를로드하려는 경우에도 매우 편리 할 수 ​​있습니다. 생성 된 트리의 노드에 추가 데이터를 쉽게 추가 할 수도 있습니다.

면책 조항,이 패키지를 만들었습니다.


1

며칠 전에 플랫 어레이에서 폴더 트리를 표시해야 할 때 비슷한 문제가 발생했습니다. 여기서 TypeScript의 솔루션을 보지 못 했으므로 도움이 되길 바랍니다.

제 경우에는 주 부모가 단 하나였으며 rawData 배열도 정렬 할 필요가 없습니다. 다음과 같은 임시 개체 준비에 기반한 솔루션 {parentId: [child1, child2, ...] }

원시 데이터 예

const flatData: any[] = Folder.ofCollection([
  {id: '1', title: 'some title' },
  {id: '2', title: 'some title', parentId: 1 },
  {id: '3', title: 'some title', parentId: 7 },
  {id: '4', title: 'some title', parentId: 1 },
  {id: '5', title: 'some title', parentId: 2 },
  {id: '6', title: 'some title', parentId: 5 },
  {id: '7', title: 'some title', parentId: 5 },

]);

의 데프 폴더

export default class Folder {
    public static of(data: any): Folder {
        return new Folder(data);
    }

    public static ofCollection(objects: any[] = []): Folder[] {
        return objects.map((obj) => new Folder(obj));
    }

    public id: string;
    public parentId: string | null;
    public title: string;
    public children: Folder[];

    constructor(data: any = {}) {
        this.id = data.id;
        this.parentId = data.parentId || null;
        this.title = data.title;
        this.children = data.children || [];
    }
}

솔루션 : 플랫 인수에 대한 트리 구조를 반환하는 함수

    public getTree(flatData: any[]): Folder[] {
        const addChildren = (item: Folder) => {
            item.children = tempChild[item.id] || [];
            if (item.children.length) {
                item.children.forEach((child: Folder) => {
                    addChildren(child);
                });
            }
        };

        const tempChild: any = {};
        flatData.forEach((item: Folder) => {
            const parentId = item.parentId || 0;
            Array.isArray(tempChild[parentId]) ? tempChild[parentId].push(item) : (tempChild[parentId] = [item]);
        });

        const tree: Folder[] = tempChild[0];
        tree.forEach((base: Folder) => {
            addChildren(base);
        });
        return tree;
    }

0

다음은 Babel 환경에 맞게 조정 된 위의 답변을 모델로 만든 간단한 도우미 함수입니다.

import { isEmpty } from 'lodash'

export default function unflattenEntities(entities, parent = {id: null}, tree = []) {

  let children = entities.filter( entity => entity.parent_id == parent.id)

  if (!isEmpty( children )) {
    if ( parent.id == null ) {
      tree = children
    } else {
      parent['children'] = children
    }
    children.map( child => unflattenEntities( entities, child ) )
  }

  return tree

}

0

다음은 일반 ES5이며 최상위 수준과 자식 모두에 대해 노드 배열을 반환하는 대신 id에 키가 지정된 객체를 반환하는 Steven Harris의 수정 된 버전입니다.

unflattenToObject = function(array, parent) {
  var tree = {};
  parent = typeof parent !== 'undefined' ? parent : {id: 0};

  var childrenArray = array.filter(function(child) {
    return child.parentid == parent.id;
  });

  if (childrenArray.length > 0) {
    var childrenObject = {};
    // Transform children into a hash/object keyed on token
    childrenArray.forEach(function(child) {
      childrenObject[child.id] = child;
    });
    if (parent.id == 0) {
      tree = childrenObject;
    } else {
      parent['children'] = childrenObject;
    }
    childrenArray.forEach(function(child) {
      unflattenToObject(array, child);
    })
  }

  return tree;
};

var arr = [
    {'id':1 ,'parentid': 0},
    {'id':2 ,'parentid': 1},
    {'id':3 ,'parentid': 1},
    {'id':4 ,'parentid': 2},
    {'id':5 ,'parentid': 0},
    {'id':6 ,'parentid': 0},
    {'id':7 ,'parentid': 4}
];
tree = unflattenToObject(arr);

0

이것은 여러 루트 항목과 함께 작동하는 위의 수정 된 버전이며, 내 ID와 parentId에 GUID를 사용하므로이를 생성하는 UI에서 루트 항목을 0000000-00000-00000-TREE-ROOT-ITEM과 같은 하드 코딩합니다.

var tree = unflatten (records, "TREE-ROOT-ITEM");

function unflatten(records, rootCategoryId, parent, tree){
    if(!_.isArray(tree)){
        tree = [];
        _.each(records, function(rec){
            if(rec.parentId.indexOf(rootCategoryId)>=0){        // change this line to compare a root id
            //if(rec.parentId == 0 || rec.parentId == null){    // example for 0 or null
                var tmp = angular.copy(rec);
                tmp.children = _.filter(records, function(r){
                    return r.parentId == tmp.id;
                });
                tree.push(tmp);
                //console.log(tree);
                _.each(tmp.children, function(child){
                    return unflatten(records, rootCategoryId, child, tree);
                });
            }
        });
    }
    else{
        if(parent){
            parent.children = _.filter(records, function(r){
                return r.parentId == parent.id;
            });
            _.each(parent.children, function(child){
                return unflatten(records, rootCategoryId, child, tree);
            });
        }
    }
    return tree;
}

0

인터넷 http://jsfiddle.net/stywell/k9x2a3g6/ 에서 복사

    function list2tree(data, opt) {
        opt = opt || {};
        var KEY_ID = opt.key_id || 'ID';
        var KEY_PARENT = opt.key_parent || 'FatherID';
        var KEY_CHILD = opt.key_child || 'children';
        var EMPTY_CHILDREN = opt.empty_children;
        var ROOT_ID = opt.root_id || 0;
        var MAP = opt.map || {};
        function getNode(id) {
            var node = []
            for (var i = 0; i < data.length; i++) {
                if (data[i][KEY_PARENT] == id) {
                    for (var k in MAP) {
                        data[i][k] = data[i][MAP[k]];
                    }
                    if (getNode(data[i][KEY_ID]) !== undefined) {
                        data[i][KEY_CHILD] = getNode(data[i][KEY_ID]);
                    } else {
                        if (EMPTY_CHILDREN === null) {
                            data[i][KEY_CHILD] = null;
                        } else if (JSON.stringify(EMPTY_CHILDREN) === '[]') {
                            data[i][KEY_CHILD] = [];
                        }
                    }
                    node.push(data[i]);
                }
            }
            if (node.length == 0) {
                return;
            } else {
                return node;
            }
        }
        return getNode(ROOT_ID)
    }

    var opt = {
        "key_id": "ID",              //节点的ID
        "key_parent": "FatherID",    //节点的父级ID
        "key_child": "children",     //子节点的名称
        "empty_children": [],        //子节点为空时,填充的值  //这个参数为空时,没有子元素的元素不带key_child属性;还可以为null或者[],同理
        "root_id": 0,                //根节点的父级ID
        "map": {                     //在节点内映射一些值  //对象的键是节点的新属性; 对象的值是节点的老属性,会赋值给新属性
            "value": "ID",
            "label": "TypeName",
        }
    };

0

npm 패키지 array-to-tree https://github.com/alferov/array-to-tree 를 사용할 수 있습니다 . 일반 노드 배열 (부모 노드에 대한 포인터 포함)을 중첩 된 데이터 구조로 변환합니다.

데이터의 데이터베이스 세트에서 검색된 데이터를 중첩 된 데이터 구조 (예 : 탐색 트리)로 변환하는 문제를 해결합니다.

용법:

var arrayToTree = require('array-to-tree');

var dataOne = [
  {
    id: 1,
    name: 'Portfolio',
    parent_id: undefined
  },
  {
    id: 2,
    name: 'Web Development',
    parent_id: 1
  },
  {
    id: 3,
    name: 'Recent Works',
    parent_id: 2
  },
  {
    id: 4,
    name: 'About Me',
    parent_id: undefined
  }
];

arrayToTree(dataOne);

/*
 * Output:
 *
 * Portfolio
 *   Web Development
 *     Recent Works
 * About Me
 */

0

이것은 내가 반응 프로젝트에서 사용한 것입니다.

// ListToTree.js
import _filter from 'lodash/filter';
import _map from 'lodash/map';

export default (arr, parentIdKey) => _map(_filter(arr, ar => !ar[parentIdKey]), ar => ({
  ...ar,
  children: _filter(arr, { [parentIdKey]: ar.id }),
}));

용법:

// somewhere.js
import ListToTree from '../Transforms/ListToTree';

const arr = [
   {
      "id":"Bci6XhCLZKPXZMUztm1R",
      "name":"Sith"
   },
   {
      "id":"C3D71CMmASiR6FfDPlEy",
      "name":"Luke",
      "parentCategoryId":"ltatOlEkHdVPf49ACCMc"
   },
   {
      "id":"aS8Ag1BQqxkO6iWBFnsf",
      "name":"Obi Wan",
      "parentCategoryId":"ltatOlEkHdVPf49ACCMc"
   },
   {
      "id":"ltatOlEkHdVPf49ACCMc",
      "name":"Jedi"
   },
   {
      "id":"pw3CNdNhnbuxhPar6nOP",
      "name":"Palpatine",
      "parentCategoryId":"Bci6XhCLZKPXZMUztm1R"
   }
];
const response = ListToTree(arr, 'parentCategoryId');

산출:

[
   {
      "id":"Bci6XhCLZKPXZMUztm1R",
      "name":"Sith",
      "children":[
         {
            "id":"pw3CNdNhnbuxhPar6nOP",
            "name":"Palpatine",
            "parentCategoryId":"Bci6XhCLZKPXZMUztm1R"
         }
      ]
   },
   {
      "id":"ltatOlEkHdVPf49ACCMc",
      "name":"Jedi",
      "children":[
         {
            "id":"C3D71CMmASiR6FfDPlEy",
            "name":"Luke",
            "parentCategoryId":"ltatOlEkHdVPf49ACCMc"
         },
         {
            "id":"aS8Ag1BQqxkO6iWBFnsf",
            "name":"Obi Wan",
            "parentCategoryId":"ltatOlEkHdVPf49ACCMc"
         }
      ]
   }
]```


0

내 typescript 솔루션은 다음과 같이 도움이 될 수 있습니다.

type ITreeItem<T> = T & {
    children: ITreeItem<T>[],
};

type IItemKey = string | number;

function createTree<T>(
    flatList: T[],
    idKey: IItemKey,
    parentKey: IItemKey,
): ITreeItem<T>[] {
    const tree: ITreeItem<T>[] = [];

    // hash table.
    const mappedArr = {};
    flatList.forEach(el => {
        const elId: IItemKey = el[idKey];

        mappedArr[elId] = el;
        mappedArr[elId].children = [];
    });

    // also you can use Object.values(mappedArr).forEach(...
    // but if you have element which was nested more than one time
    // you should iterate flatList again:
    flatList.forEach((elem: ITreeItem<T>) => {
        const mappedElem = mappedArr[elem[idKey]];

        if (elem[parentKey]) {
            mappedArr[elem[parentKey]].children.push(elem);
        } else {
            tree.push(mappedElem);
        }
    });

    return tree;
}

사용 예 :

createTree(yourListData, 'id', 'parentId');

0

@Halcyon 답변을 기반으로 ES6 버전을 작성했습니다.

const array = [
  {
    id: '12',
    parentId: '0',
    text: 'one-1'
  },
  {
    id: '6',
    parentId: '12',
    text: 'one-1-6'
  },
  {
    id: '7',
    parentId: '12',
    text: 'one-1-7'
  },

  {
    id: '9',
    parentId: '0',
    text: 'one-2'
  },
  {
    id: '11',
    parentId: '9',
    text: 'one-2-11'
  }
];

// Prevent changes to the original data
const arrayCopy = array.map(item => ({ ...item }));

const listToTree = list => {
  const map = {};
  const roots = [];

  list.forEach((v, i) => {
    map[v.id] = i;
    list[i].children = [];
  });

  list.forEach(v => (v.parentId !== '0' ? list[map[v.parentId]].children.push(v) : roots.push(v)));

  return roots;
};

console.log(listToTree(arrayCopy));

이 알고리즘의 원리는 "맵"을 사용하여 인덱스 관계를 설정하는 것입니다. "parentId"로 목록에서 "item"을 찾고 각 "item"에 "children"을 추가하는 것은 쉽습니다. "list"는 참조 관계이므로 "roots"는 전체 트리와의 관계를 구축합니다.


0

비슷한 질문에 답하십시오.

https://stackoverflow.com/a/61575152/7388356

최신 정보

MapES6에 도입 된 객체 를 사용할 수 있습니다 . 기본적으로 배열을 다시 반복하여 부모를 찾는 대신 인덱스로 배열의 항목을 가져 오는 것처럼 부모의 ID로 배열에서 부모 항목을 가져옵니다.

다음은 간단한 예입니다.

const people = [
  {
    id: "12",
    parentId: "0",
    text: "Man",
    level: "1",
    children: null
  },
  {
    id: "6",
    parentId: "12",
    text: "Boy",
    level: "2",
    children: null
  },
  {
    id: "7",
    parentId: "12",
    text: "Other",
    level: "2",
    children: null
  },
  {
    id: "9",
    parentId: "0",
    text: "Woman",
    level: "1",
    children: null
  },
  {
    id: "11",
    parentId: "9",
    text: "Girl",
    level: "2",
    children: null
  }
];

function toTree(arr) {
  let arrMap = new Map(arr.map(item => [item.id, item]));
  let tree = [];

  for (let i = 0; i < arr.length; i++) {
    let item = arr[i];

    if (item.parentId !== "0") {
      let parentItem = arrMap.get(item.parentId);

      if (parentItem) {
        let { children } = parentItem;

        if (children) {
          parentItem.children.push(item);
        } else {
          parentItem.children = [item];
        }
      }
    } else {
      tree.push(item);
    }
  }

  return tree;
}

let tree = toTree(people);

console.log(tree);

crazy-williams-glgj3 편집


1
이 링크가 질문에 답할 수 있지만 여기에 답변의 필수 부분을 포함하고 참조 용 링크를 제공하는 것이 좋습니다. 링크 된 페이지가 변경되면 링크 전용 답변이 무효화 될 수 있습니다. - 리뷰에서
JeffRSon

Ok, 주요 아이디어를 추가하고 샘플 예제를 제공했습니다.
Yusufbek

0

를 기반으로 FurkanO의 대답 @ , 나는 (Dac0d3r 요청 @ 같은) origial 데이터를 변이하지 않는 다른 버전을 만들었습니다. 나는 @shekhardtu의 대답을 정말 좋아 했지만 여러 번 데이터를 필터링해야한다는 것을 깨달았습니다. 먼저 데이터를 복사하여 FurkanO의 답변을 사용할 수 있다고 생각했습니다. 나는 jsperf 에서 내 버전을 시도 했고 , 불행히도 (매우) 암울한 결과를 얻었습니다. 받아 들여진 대답이 정말 좋은 것 같습니다! 내 버전은 구성이 가능하고 안전하므로 어쨌든 여러분과 공유합니다. 내 공헌은 다음과 같습니다.

function unflat(data, options = {}) {
    const { id, parentId, childrenKey } = {
        id: "id",
        parentId: "parentId",
        childrenKey: "children",
        ...options
    };
    const copiesById = data.reduce(
        (copies, datum) => ((copies[datum[id]] = datum) && copies),
        {}
    );
    return Object.values(copiesById).reduce(
        (root, datum) => {
            if ( datum[parentId] && copiesById[datum[parentId]] ) {
                copiesById[datum[parentId]][childrenKey] = [ ...copiesById[datum[parentId]][childrenKey], datum ];
            } else {
                root = [ ...root, datum ];
            }
            return root
        }, []
    );
}

const data = [
    {
        "account": "10",
        "name": "Konto 10",
        "parentAccount": null
    },{
        "account": "1010",
        "name": "Konto 1010",
        "parentAccount": "10"
    },{
        "account": "10101",
        "name": "Konto 10101",
        "parentAccount": "1010"
    },{
        "account": "10102",
        "name": "Konto 10102",
        "parentAccount": "1010"
    },{
        "account": "10103",
        "name": "Konto 10103",
        "parentAccount": "1010"
    },{
        "account": "20",
        "name": "Konto 20",
        "parentAccount": null
    },{
        "account": "2020",
        "name": "Konto 2020",
        "parentAccount": "20"
    },{
        "account": "20201",
        "name": "Konto 20201",
        "parentAccount": "2020"
    },{
        "account": "20202",
        "name": "Konto 20202",
        "parentAccount": "2020"
    }
];

const options = {
    id: "account",
    parentId: "parentAccount",
    childrenKey: "children"
};

console.log(
    "Hierarchical tree",
    unflat(data, options)
);

options 매개 변수를 사용하면 ID 또는 상위 ID로 사용할 속성을 구성 할 수 있습니다. 누군가가 원 "childNodes": []하거나 무언가를 원하면 자식 속성의 이름을 구성하는 것도 가능합니다 .

OP는 단순히 기본 옵션을 사용할 수 있습니다.

input.People = unflat(input.People);

부모 ID가 falsy (인 경우 null, undefined또는 다른 falsy 값) 또는 부모 개체가 존재하지 않는, 우리는 루트 노드로 객체를 고려한다.


-1
  1. 타사 라이브러리없이
  2. 사전 주문 어레이 필요 없음
  3. 당신은 당신이 원하는 나무의 어떤 부분을 얻을 수 있습니다

이 시도

function getUnflatten(arr,parentid){
  let output = []
  for(const obj of arr){
    if(obj.parentid == parentid)

      let children = getUnflatten(arr,obj.id)

      if(children.length){
        obj.children = children
      }
      output.push(obj)
    }
  }

  return output
 }

Jsfiddle에서 테스트


-1

노드 배열을 트리로 변환

노드 배열 ( 부모 ID로 관련됨 )을 트리 구조 로 변환하는 ES6 함수 :

/**
 * Convert nodes list related by parent ID - to tree.
 * @syntax getTree(nodesArray [, rootID [, propertyName]])
 *
 * @param {Array} arr   Array of nodes
 * @param {integer} id  Defaults to 0
 * @param {string} p    Property name. Defaults to "parent_id"
 * @returns {Object}    Nodes tree
 */

const getTree = (arr, p = "parent_id") => arr.reduce((o, n) => {

  if (!o[n.id]) o[n.id] = {};
  if (!o[n[p]]) o[n[p]] = {};
  if (!o[n[p]].nodes) o[n[p]].nodes= [];
  if (o[n.id].nodes) n.nodes= o[n.id].nodes;

  o[n[p]].nodes.push(n);
  o[n.id] = n;

  return o;
}, {});

노드 트리에서 HTML 목록 생성

트리가 제자리에 있으면 UL> LI 요소를 빌드 하는 재귀 함수 가 있습니다.

/**
 * Convert Tree structure to UL>LI and append to Element
 * @syntax getTree(treeArray [, TargetElement [, onLICreatedCallback ]])
 *
 * @param {Array} tree Tree array of nodes
 * @param {Element} el HTMLElement to insert into
 * @param {function} cb Callback function called on every LI creation
 */

const treeToHTML = (tree, el, cb) => el.append(tree.reduce((ul, n) => {
  const li = document.createElement('li');

  if (cb) cb.call(li, n);
  if (n.nodes?.length) treeToHTML(n.nodes, li, cb);

  ul.append(li);
  return ul;
}, document.createElement('ul')));

데모 시간

다음은 노드의 선형 배열을 갖고 위의 두 기능을 모두 사용하는 예입니다.

const getTree = (arr, p = "parent_id") => arr.reduce((o, n) => {
  if (!o[n.id]) o[n.id] = {};
  if (!o[n[p]]) o[n[p]] = {};
  if (!o[n[p]].nodes) o[n[p]].nodes = [];
  if (o[n.id].nodes) n.nodes = o[n.id].nodes;
  o[n[p]].nodes.push(n);
  o[n.id] = n;
  return o;
}, {});


const treeToHTML = (tree, el, cb) => el.append(tree.reduce((ul, n) => {
  const li = document.createElement('li');
  if (cb) cb.call(li, n);
  if (n.nodes?.length) treeToHTML(n.nodes, li, cb);
  ul.append(li);
  return ul;
}, document.createElement('ul')));


// DEMO TIME:

const nodesList = [
  {id: 10,  parent_id: 4,  text: "Item 10"}, // PS: Order does not matters
  {id: 1,   parent_id: 0,  text: "Item 1"},  
  {id: 4,   parent_id: 0,  text: "Item 4"},
  {id: 3,   parent_id: 5,  text: "Item 3"},
  {id: 5,   parent_id: 4,  text: "Item 5"},
  {id: 2,   parent_id: 1,  text: "Item 2"},
];
const myTree = getTree(nodesList)[0].nodes; // Get nodes of Root (0)

treeToHTML(myTree, document.querySelector("#tree"), function(node) {
  this.textContent = `(${node.parent_id} ${node.id}) ${node.text}`;
  this._node = node;
  this.addEventListener('click', clickHandler);
});

function clickHandler(ev) {
  if (ev.target !== this) return;
  console.clear();
  console.log(this._node.id);
};
<div id="tree"></div>


-1

이것은 오래된 스레드이지만 ES6을 사용하면 업데이트가 결코 아프지 않다고 생각했습니다.

const data = [{
    id: 1,
    parent_id: 0
}, {
    id: 2,
    parent_id: 1
}, {
    id: 3,
    parent_id: 1
}, {
    id: 4,
    parent_id: 2
}, {
    id: 5,
    parent_id: 4
}, {
    id: 8,
    parent_id: 7
}, {
    id: 9,
    parent_id: 8
}, {
    id: 10,
    parent_id: 9
}];

const arrayToTree = (items=[], id = null, link = 'parent_id') => items.filter(item => id==null ? !items.some(ele=>ele.id===item[link]) : item[link] === id ).map(item => ({ ...item, children: arrayToTree(items, item.id) }))
const temp1=arrayToTree(data)
console.log(temp1)

const treeToArray = (items=[], key = 'children') => items.reduce((acc, curr) => [...acc, ...treeToArray(curr[key])].map(({ [`${key}`]: child, ...ele }) => ele), items);
const temp2=treeToArray(temp1)

console.log(temp2)

누군가에게 도움이되기를 바랍니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.