A great opportunity to learn about reusable modules and mutual recursion. This solution in this answer solves your specific problem without any modification of the modules written in other answers.
First, to solve the sorting portion of the problem we will compare by count
using the Comparison module, presented in this Q&A. If this feels strange to you, check out the post to see a wide variety of ways in which the module is useful -
import * as Comparison from "./Comparison"
const byCount =
Comparison.map // map a comparison
( Comparison.desc // default "descending" comparison
, ({ company: { count = 0 }}) => count // path to count property
)
// ...
Second, to build the required tree
using the Tree module, presented in this Q&A -
// ...
import { tree } from "./Tree"
const foreignKey = ({ company: { parent_id }}) =>
parent_id
const input =
[{company: {id: 1, count: 10}}, {company: {id: 2, parent_id: 1, count: 3}}, {company: {id: 3, parent_id: 1, count: 5}}, {company: {id: 4, parent_id: 2, count: 4}}, {company: {id: 5, count: 1}}]
const result =
tree
( input
, foreignKey // <- your foreign key
, (node, get) =>
( { ...node
, children:
get(node.company.id) // <- your primary key
.sort(byCount) // <- sort children
}
)
)
.sort(byCount) // <- sort root elements
console.log(JSON.stringify(result, null, 2))
Output; sibling nodes are sorted by count
in descending order -
[
{
"company": {
"id": 1,
"count": 10
},
"children": [
{
"company": {
"id": 3,
"parent_id": 1,
"count": 5
},
"children": []
},
{
"company": {
"id": 2,
"parent_id": 1,
"count": 3
},
"children": [
{
"company": {
"id": 4,
"parent_id": 2,
"count": 4
},
"children": []
}
]
}
]
},
{
"company": {
"id": 5,
"count": 1
},
"children": []
}
]
branching implementations
@ScottSauyet makes me consider alternate designs for tree
, especially considering renaming it to forest
. One particular improvement I had in mind was to make tree
simply take a pair of primary and foreign keys along with a child transform function -
// Main.js
const input =
// ...
const byCount =
// ...
const fk = ({ company: { parent_id }}) =>
parent_id
const pk = ({ company: { id }}) =>
id
const result =
tree
( input
, [ pk, fk ] // <- primary/foreign key pair
, children => ({ children: children.sort(byCount) }) // <- transform
)
.sort(byCount)
console.log(JSON.stringify(result, null, 2))
The result is valid of course, so long as we update tree
to make it so! -
// Tree.js (revised)
function tree (all, [pk, fk], maker, root) // <-
{ const cache =
index(all, fk)
const many = (all = []) =>
all.map(x => one(x))
const one = (single) =>
({ ...single, ...maker(many(cache.get(pk(single)))) }) // <-
return many(cache.get(root))
}
One downside to this is single
and (return value of) maker
are assumed to be objects, {...}
, and so it restricts some of the tree shapes you can build. A more sophisticated merge could use runtime type-checking but I'm wary to go down that path. Another downside is all properties of node
are included in the result whereas before we could pick all/any/none.