Dynamically calculate the average for a nested collection using lodash

前端 未结 4 528
予麋鹿
予麋鹿 2021-01-27 17:03

I have a JSON array of objects (a collection) like:

[{
  \"x\": {
        \"x1\": 1
  },
  \"y\": {
    \"yt\": 0,
    \"zt\": 4,
    \"qa\": 3,
    \"ft\": 0,
          


        
相关标签:
4条回答
  • 2021-01-27 17:22

    You could first collect and sum all values in the same data structure and then calculkate the average by a division with the length of the given array.

    function getParts(array, result) {
        function iter(o, r) {
            Object.keys(o).forEach(function (k) {
                if (o[k] && typeof o[k] === 'object') {
                    return iter(o[k], r[k] = r[k] || {});
                }
                r[k] = (r[k] || 0) + o[k];
            });
        }
    
        function avr(o) {
            Object.keys(o).forEach(function (k) {
                if (o[k] && typeof o[k] === 'object') {
                    return avr(o[k]);
                }
                o[k] = o[k] /data.length;
            });
        }
    
        data.forEach(function (a) {
            iter(a, result);
        });
        avr(result);
    }
    
    var data = [{ x: { x1: 1 }, y: { yt: 0, zt: 4, qa: 3, ft: 0, } }, { x: { x1: 5 }, y: { yt: 10, zt: 2, qa: 0, ft: 0, } }],
        result = {};
    
    getParts(data, result);
    
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    0 讨论(0)
  • 2021-01-27 17:23

    let objectArray = [{
      "x": {
            "x1": 1
      },
      "y": {
        "yt": 0,
        "zt": 4,
        "qa": 3,
        "ft": 0,
      }
    },
    {
      "x": {
            "x1": 5
      },
      "y": {
        "yt": 10,
        "zt": 2,
        "qa": 0,
        "ft": 0,
      }
    }];
    
    function findAverage(array) {
      
      let counter = {},
        result = {},
        i,
        obj,
        key,
        subKey;
      
      // Iterate through array
      for (i = 0; i < array.length; i++) {
        obj = array[i];
        // Copy each key in array element to counter object
        for (key in obj) {
          counter[key] = counter[key] || {};
          // Increment and keep count of key-values of counter based on values in array element
          for (subKey in obj[key]) {
            counter[key][subKey] = counter[key][subKey] || {total: 0, numElements: 0};
            counter[key][subKey].total += obj[key][subKey];
            counter[key][subKey].numElements += 1;
          }
        }
      }
      // Go back through counter to find average of all existing subkeys (based on incremented total and the number of elements recorded) and throw it into result object
      for (key in counter) {
        result[key] = result[key] || {};
        
        for (subKey in counter[key]) {
          result[key][subKey] = counter[key][subKey].total / counter[key][subKey].numElements;
        }
      }
      
      return result;
    }
    
    console.log(findAverage(objectArray));

    Not designed to be absolutely optimal, and copying objects can be done recursively without knowing in advance their structure, but I wanted to keep the steps as clear as possible.

    Edited to allow testing as snippet. Had no idea you could even do that on SO!

    0 讨论(0)
  • 2021-01-27 17:24

    var array = [{
      "x": {
            "x1": 1
      },
      "y": {
        "yt": 0,
        "zt": 4,
        "qa": 3,
        "ft": 0
      }
    },
    {
      "x": {
            "x1": 5
      },
      "y": {
        "yt": 10,
        "zt": 2,
        "qa": 0,
        "ft": 0
      }
    }];
    function aintob(){
      var o = {};
      var first = array[0],
          second = array[1];
      var result = {x:{},y:{}};
      var each = function(letter, oa, ob){
        var i,
        letter = {};
        for(i in oa){
          letter[i] = (oa[i]+ob[i])/2;
        }
        return letter;
      }
      o.x = each("x", first.x, second.x);
      o.y = each("y", first.y, second.y);
      return o;
    }
    console.log(aintob());

    0 讨论(0)
  • 2021-01-27 17:36

    You can merge the objects using the spread syntax and lodash's _.mergeWith(). When merging, if the 2nd parameter (b) is a number divide it by the number of items in the original array to get it's respective contribution to the total average. If the 1st parameter (a) is a number, just add it without dividing (to avoid dividing the sum multiple times), or add 0 if it's undefined.

    I've added examples of 2 objects array, and 3 objects array.

    const getAvg = (data) => _.mergeWith({}, ...data, (a, b) => {
      if(_.isNumber(b)) {
        return ((b || 0) / data.length) + (_.isNumber(a) ? (a || 0) : 0);
      }
    });
    
    const data1 = [
    {"x":{"x1":1},"y":{"yt":0,"zt":4,"qa":3,"ft":0}},
    {"x":{"x1":5},"y":{"yt":10,"zt":2,"qa":0,"ft":0}}
    ];
    
    const data2 = [
    {"x":{"x1":1},"y":{"yt":0,"zt":4,"qa":3,"ft":0}},
    {"x":{"x1":5},"y":{"yt":10,"zt":2,"qa":0,"ft":0}},
    {"x":{"x1":3},"y":{"yt":2,"zt":6,"qa":3,"ft":0}}
    ];
    
    const result1 = getAvg(data1);
    
    console.log('2 objects in the array: ', result1);
    
    const result2 = getAvg(data2);
    
    console.log('3 objects in the array: ', result2);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

    0 讨论(0)
提交回复
热议问题