randomKek
randomKek

Reputation: 1128

Recursive array of objects to append property children count

I need to recursively loop through an array of objects and each object object has a property label which needs to be modified to include the children count.

Take a look at this example:

const nodes = [{
  value: 'World',
  label: 'World',
  children: [{
    label: 'Europe',
    value: 'Europe',
    children: [
      {
        label: 'Albania',
        value: 'AL'
      },
      {
        label: 'BeNeLux',
        value: 'BeNeLux',
        children: [
          {
            label: 'The Netherlands',
            value: 'NL'
          },
          {
            label: 'Belgium',
            value: 'BE'
          },
          {
            label: 'Luxembourg',
            value: 'LU'
          }
        ]
      }
    ]
  }]
}]

The expected output would be:

const expectedOutput = [{
  value: 'World',
  label: 'World (4)',
  children: [{
    label: 'Europe (4)',
    value: 'Europe',
    children: [
      {
        label: 'Albania',
        value: 'AL'
      },
      {
        label: 'BeNeLux (3)',
        value: 'BeNeLux',
        children: [
          {
            label: 'The Netherlands',
            value: 'NL'
          },
          {
            label: 'Belgium',
            value: 'BE'
          },
          {
            label: 'Luxembourg',
            value: 'LU'
          }
        ]
      }
    ]
  }]
}]

This is what I have working now, but it's not working correctly because as mentioned in the expectedOutput above, Europe's label would be Europe (4) and my version counts Europe (2) because it's ignoring the children inside Europe.

export const getSortedNodesWithChildrenCountLabel = nodes => {
  return nodes
    .reduce(function f (output, node) {
      if (node?.children) {
        node.label += ` (${node.children.length})`
        node.children = node.children
          .reduce(f, [])
      }

      output.push(node)
      return output
    }, [])
}

Upvotes: 4

Views: 1371

Answers (2)

Nina Scholz
Nina Scholz

Reputation: 386680

You could take a recursive approach and get the count from the children and update label.

This approach mutates the data.

function update(nodes) {
    return nodes.reduce((count, node) => {
        if (node.children) {
            var subcount = update(node.children);
            node.label += ` (${subcount})`;
            return count + subcount;
        }
        return count + 1;
    }, 0);
}

const nodes = [{ value: 'World', label: 'World', children: [{ label: 'Europe', value: 'Europe', children: [{ label: 'Albania', value: 'AL' }, { label: 'BeNeLux', value: 'BeNeLux', children: [{ label: 'The Netherlands', value: 'NL' }, { label: 'Belgium', value: 'BE' }, { label: 'Luxembourg', value: 'LU' }] }] }] }];

update(nodes);

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

A non mutating version which gets the count with an object to the outer call.

function fn(count = { count: 0 }) {
    return function ({ value, label, children }) {
        var sub = { count: 0 };
        if (children) {
            children = children.map(fn(sub)),
            label += ` (${sub.count})`;
            count.count += sub.count;
            return { value, label, children };
        }
        count.count++;
        return { value, label };
    };
}

const
    nodes = [{ value: 'World', label: 'World', children: [{ label: 'Europe', value: 'Europe', children: [{ label: 'Albania', value: 'AL' }, { label: 'BeNeLux', value: 'BeNeLux', children: [{ label: 'The Netherlands', value: 'NL' }, { label: 'Belgium', value: 'BE' }, { label: 'Luxembourg', value: 'LU' }] }] }] }],
    withCount = nodes.map(fn());        

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

Upvotes: 2

antonku
antonku

Reputation: 7675

You can decompose children counting into a separate recursive function:

const nodes = [{
  value: 'World',
  label: 'World',
  children: [{
    label: 'Europe',
    value: 'Europe',
    children: [
      {
        label: 'Albania',
        value: 'AL'
      },
      {
        label: 'BeNeLux',
        value: 'BeNeLux',
        children: [
          {
            label: 'The Netherlands',
            value: 'NL'
          },
          {
            label: 'Belgium',
            value: 'BE'
          },
          {
            label: 'Luxembourg',
            value: 'LU'
          }
        ]
      }
    ]
  }]
}]

const getChildrenCount = (node, count = 0) => {
  if (!node.children) {
    return 1
  }
  for (const child of node.children) {
    count += getChildrenCount(child)
  }
  return count;
}

const getSortedNodesWithChildrenCountLabel = nodes => {
  return nodes
    .reduce(function f (output, node) {
      if (node.children) {
        node.label += ` (${getChildrenCount(node)})`
        node.children = node.children
          .reduce(f, [])
      }

      output.push(node)
      return output
    }, [])
}

console.log(getSortedNodesWithChildrenCountLabel(nodes))

Upvotes: 2

Related Questions