Home > OS >  Generate aggregate at each level till parent
Generate aggregate at each level till parent

Time:10-07

Given a JSON like below

{
  "main": {
    "sub1": {
      "cat": {
        "subcat1":{
          "count": 1
        },
        "subcat2":{
          "count": 2
        }
      }
    },
    "sub2": {
      "cat": {
        "subcat1":{
          "count": 3
        },
        "subcat2":{
          "count": 5
        }
      }
    }
  }
}

Need to aggregate count at child level to its immediate parent till top-level parent like below

{
  "main": {
    "count": 11,
    "sub1": {
      "count": 3,
      "cat": {
        "count": 3,
        "subcat1":{
          "count": 1
        },
        "subcat2":{
          "count": 2
        }
      }
    },
    "sub2": {
      "count": 8,
      "cat": {
        "count": 8,
        "subcat1":{
          "count": 3
        },
        "subcat2":{
          "count": 5
        }
      }
    }
  }
}

Tried to think of logic for the same, could not get to write anything. What would be right code/logic for same? One this is for sure, that i will nee some kind of recursion that keeps adding counts till parent level.

CodePudding user response:

You can write recount using simple recursion. This solution depends on the leaf nodes (deepest nesting) to contain a numeric count property -

function recount ({count, ...t}) {
  if (count != null) {
    return { ...t, count }
  }
  else {
    const children =
      Object.entries(t).map(([k, v]) => [k, recount(v)])
    return {
      count: children.reduce((r, [_, {count}]) => r   count, 0),  
      ...Object.fromEntries(children)
    }
  }
}

const myinput =
  {main:{sub1:{cat:{subcat1:{count:1},subcat2:{count:2}}},sub2:{cat:{subcat1:{count:3},subcat2:{count:5}}}}}

console.log(recount(myinput))
        

{
  "count": 11,
  "main": {
    "count": 11,
    "sub1": {
      "count": 3,
      "cat": {
        "count": 3,
        "subcat1": {
          "count": 1
        },
        "subcat2": {
          "count": 2
        }
      }
    },
    "sub2": {
      "count": 8,
      "cat": {
        "count": 8,
        "subcat1": {
          "count": 3
        },
        "subcat2": {
          "count": 5
        }
      }
    }
  }
}

CodePudding user response:

const data = {
  "main": {
    "sub1": {
      "cat": {
        "subcat1":{
          "count": 1
        },
        "subcat2":{
          "count": 2
        }
      }
    },
    "sub2": {
      "cat": {
        "subcat1":{
          "count": 3
        },
        "subcat2":{
          "count": 5
        }
      }
    }
  }
};

const getCount = (thing) => {
  if (!thing.count) {
    thing.count = Object.values(thing).reduce((acc, el) => acc   getCount(el), 0);
  }
  return thing.count;
}

const n = getCount(data);
console.log(n);
console.log(data);

CodePudding user response:

This is not very different from the answer from Mulan, but it shows the same process working with somewhat different syntax:

const sum = (ns) => ns .reduce ((a, b) => a   b, 0)

const total = (
  {count, ...rest},
  kids = Object .entries (rest) .map (([k, v]) => [k, total (v)]),
  kidsCount = sum (kids .map (([k, v]) => v .count))
) => count == undefined 
  ? Object .fromEntries ([['count', kidsCount], ...kids])
  : {count, ...rest}

const data = {main: {sub1: {cat: {subcat1: {count: 1}, subcat2: {count: 2}}}, sub2: {cat: {subcat1: {count: 3}, subcat2: {count: 5}}}}}

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

We pull out a count property if it exists, then recur on the other properties of this object, leaving them in a useful form for Object .fromEntries. From those, we extract and sum the count properties, and then, if the count already exists, we return a copy of the original object, and if not, we add one more entry to the kids properties, and call Object .fromEntries on that.

Note that we add a count property to the root here, which wasn't in your requested output, but simply makes sense to me. If you don't want that, you can add a wrapper function, perhaps something like (data) => ({main: total (data .main || {})}).

This uses some optional, defaulted parameters. There are times when that is a bad idea. If you want to avoid them, we can include them in an IIFE instead, like this:

const total = ({count, ...rest}) => ((
  kids = Object .entries (rest) .map (([k, v]) => [k, total (v)]),
  kidsCount = sum (kids .map (([k, v]) => v .count))
) => count == undefined 
  ? Object .fromEntries ([['count', kidsCount], ...kids])
  : {count, ...rest}
) () 

or we can just follow the technique from Mulan, where a local variable means you don't need them.

  • Related