Sort by a hierarchy
I came across this problem a few weeks ago and when I searched for a solution, I couldn't find one. As it turns out, it's a pretty straightforward algorithms application once you see how to do it; I thought it was interesting enough to share.
I had an unsorted list of records that were related by an arbitrary number of parent-child relations. I wanted to present the list with the unparented records first, with their children beneath them, their children beneath them, etc. So, given a list like:
Id | Name | Parent | |
---|---|---|---|
0009 | Piotr Nickolevitch | 0007 | |
0001 | Charles Xavier | ||
0008 | James Logan | 0007 | |
0013 | James Proudstar | 0007 | |
0014 | Eric Lensherr | ||
0005 | Bobby Drake | 0002 | |
0007 | Ororo Munroe | 0001 | |
0012 | Kurt Wagner | 0007 | |
0010 | Kitty Pryde | 0007 | |
0003 | Jean Grey | 0002 | |
0002 | Scott Summers | 0001 | |
0004 | Warren Worthington | 0002 | |
0006 | Hank McCoy | 0002 | |
0011 | Lockheed | 0010 |
- Charles Xavier
- Scott Summers
- Jean Grey
- Warren Worthington
- Bobby Drake
- Hank McCoy
- Ororo Munroe
- James Logan
- Piotr Nickolevitch
- Kitty Pryde
- Lockheed
- Kurt Wagner
- James Proudstar
- Scott Summers
- Eric Lensherr
Id
, Name
, and Boss
, my first pass will
be:
var idsToElements = {};
for (var i = 0; i < input.length; i++) {
idsToElements[input[i].Id] = input[i];
}
This just gives me a quick lookup of Id
s to the actual elements that they
represent. Now, go through the main list and create a sublist of each element's children in-place:
var topLevel = [];
for (var i = 0; i < input.length; i++) {
if (input[i].Boss) {
var parentElement = idsToElements[input[i].Boss];
if (!parentElement) {
// Parent element missing in list; treat as top-level
topLevel.push(input[i]);
} else {
if (!parentElement.children) {
parentElement.children = [];
}
parentElement.children.push(input[i]);
}
} else {
topLevel.push(input[i]);
}
}
As I encounter each element, I find its parent (by Id) in the hash structure
I created in listing 1. Then I append the element to the children
array on the parent element. If it doesn't have a parent element,
it goes into the new de-facto parent topLevel
.
Now, topLevel
is an array of the elements that either a)
didn't have parent records or b) had parent records, but those parent records
weren't found (depending on the application, this may or may not represent
an error condition). Since each parent has an array of children, each can
be sub-sorted if necessary. They can be output in a hierarchy via:
function traverse(list) {
var html = '<ul>';
for (var i = 0; i < list.length; i++) {
html += '<li>' + list[i].Name;
if (list[i].children) {
html += traverse(list[i].children);
}
html += '</li>';
}
html += '</ul>';
return html;
}
html = traverse(topLevel);
document.write(html); // Or $('document.body').append(html), if you're into that sort of thing.
Boss
in listing 2 with a dynamic reference to the parent
field, as in:
var topLevel = [];
var parentFieldName = 'Boss';
for (var i = 0; i < input.length; i++) {
if (input[i][parentFieldName])
var parentElement = idsToElements[input[i][parentFieldName]]
if (!parentElement) {
// Parent element missing in list; treat as top-level
topLevel.push(input[i]);
} else {
if (!parentElement.children) {
parentElement.children = [];
}
parentElement.children.push(input[i]);
}
} else {
topLevel.push(input[i]);
}
}
Simple enough once you realize that you're looking at a tree structure, but
you have to realize what you're looking at first.