0

I need to sum key values (K1, K2, K3...) of machines (M1, M2, M3...) for each shift (S1, S2, S3) and create a new object (Given below)

I lost my patience with jquery $.each and forEach methods (since I need to handle 5 levels of objects) and came across UnderscoreJS.

I understand that I should use multiple combinations of functions for objects http://underscorejs.org/#objects but due to being a pure front-end guy with minimal knowledge in queries and DB, I couldn't find the right way to do that.

Given JSON structure:

var data = {
    "28-11":{
        "S1":{
            "M1":{
                "K1": 10,
                "K2": 12,
                "K3": 15
            },
            "M2":{
                "K1": 8,
                "K2": 6,
                "K3": 5
            }
        },
        "S2":{
            "M1":{
                "K1": 8,
                "K2": 6,
                "K3": 5
            },
            "M2":{
                "K1": 10,
                "K2": 12,
                "K3": 15
            }
        }
    }
}

I need to obtain the following:

var allShiftsData = {
  "28-11":{
    "M1":{
      "K1": 18,
      "K2": 18,
      "K3": 20
    },
    "M2":{
      "K1": 18,
      "K2": 18,
      "K3": 20
    }
  }
}
0

4 Answers 4

1

One way to solve this is a 4-level iteration over each object properties, summing machines into the result as we come across them:

const data = {"28-11":{"S1":{"M1":{"K1":10,"K2":12,"K3":15},"M2":{"K1":8,"K2":6,"K3":5}},"S2":{"M1":{"K1":8,"K2":6,"K3":5},"M2":{"K1":10,"K2":12,"K3":15}}}};

const {
  keys
} = Object;

const each = (obj, cb) => {
  for (const prop of keys(obj)) {
    cb(obj[prop], prop);
  }
};

const sumShiftsPerDate = (data) => {
  const result = {};
  
  each(data, (shifts, date) => {
    result[date] = {};

    each(shifts, (machines, shift) => {
      each(machines, (machineKeys, machine) => {
        result[date][machine] = result[date][machine] || {};

        each(machineKeys, (keyValue, keyName) => {
          result[date][machine][keyName] = 
            (result[date][machine][keyName] || 0) + keyValue;
        });
      });
    });
  });

  return result;
};

console.log(sumShiftsPerDate(data));

Or the equivalent using underscore's each:

const data = {"28-11":{"S1":{"M1":{"K1":10,"K2":12,"K3":15},"M2":{"K1":8,"K2":6,"K3":5}},"S2":{"M1":{"K1":8,"K2":6,"K3":5},"M2":{"K1":10,"K2":12,"K3":15}}}};

const sumShiftsPerDate = (data) => {
  const result = {};
  
  _.each(data, (shifts, date) => {
    result[date] = {};

    _.each(shifts, (machines, shift) => {
      _.each(machines, (machineKeys, machine) => {
        result[date][machine] = result[date][machine] || {};

        _.each(machineKeys, (keyValue, keyName) => {
          result[date][machine][keyName] = 
            (result[date][machine][keyName] || 0) + keyValue;
        });
      });
    });
  });

  return result;
};

console.log(sumShiftsPerDate(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

A more functional approach could be to use object mapping to convert each date object from a shifts container into a machines container and use reduction on the shifts to convert them into sums of machine key values.

const data = {"28-11":{"S1":{"M1":{"K1":10,"K2":12,"K3":15},"M2":{"K1":8,"K2":6,"K3":5}},"S2":{"M1":{"K1":8,"K2":6,"K3":5},"M2":{"K1":10,"K2":12,"K3":15}}}};

const {
  assign, keys
} = Object;

const reduceObject = (obj, cb, initial) => keys(obj).
  reduce((acc, prop) => cb(acc, obj[prop], prop), initial);

const mapObject = (obj, cb) => reduceObject(obj, (result, val, prop) =>
  assign(result, {
    [prop]: cb(val, prop)
  }), {});

const sumObjects = (first, second = {}) =>
  reduceObject(first, (result, val, prop) =>
    assign(result, {
      [prop]: (val || 0) + (second[prop] || 0)
    }), {}
  );

const sumShiftsPerDate = (data) =>
  mapObject(data, (shifts, date) =>
    reduceObject(shifts, (result, shift) =>
      reduceObject(shift, (result, machineKeys, machineName) =>
        assign(result, {
          [machineName]: sumObjects(machineKeys, result[machineName])
        }),
        result
      ),
      {}
    )
  );

console.log(sumShiftsPerDate(data));

Here's the same approach using underscore's keys, extend, object, defaults, mapObject and reduce, instead of Object.keys, Object.assign, ES6+ default parameters, ES6+ dynamic keys and the custom functions mapObject and reduceObject:

const data = {"28-11":{"S1":{"M1":{"K1":10,"K2":12,"K3":15},"M2":{"K1":8,"K2":6,"K3":5}},"S2":{"M1":{"K1":8,"K2":6,"K3":5},"M2":{"K1":10,"K2":12,"K3":15}}}};

// throw in a mixin just for fun :)
// http://underscorejs.org/#mixin
_.mixin({
  sumObjects: (first, second) => 
    _.reduce(first, (result, val, prop) =>
      _.extend(result, _.object(
        [prop], [(val || 0) + (second[prop] || 0)]
      )), {}
    )
});

const sumShiftsPerDate = (data) =>
  _.mapObject(data, (shifts) =>
    _.reduce(shifts, (result, machines) =>
      _.reduce(machines, (result, machineKeys, machineName) =>
        _.extend(result, _.object(
          [machineName], [_.sumObjects(
            machineKeys,
            _.defaults({}, result[machineName]))]
        )),
        result
      ),
      {}
    )
  );

console.log(sumShiftsPerDate(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

Note: The plain JS solution has two advantages over the underscore solution:

  1. It uses native methods which are generally faster and more stable
  2. It doesn't load an entire library just for the couple methods it needs
Sign up to request clarification or add additional context in comments.

Comments

0

You could use Object.keys and Array.forEach to achieve what you need.

var data = {
  "28-11": {
    "S1": {
      "M1": {
        "K1": 10,
        "K2": 12,
        "K3": 15
      },
      "M2": {
        "K1": 8,
        "K2": 6,
        "K3": 5
      }
    },
    "S2": {
      "M1": {
        "K1": 8,
        "K2": 6,
        "K3": 5
      },
      "M2": {
        "K1": 10,
        "K2": 12,
        "K3": 15
      }
    }
  }
};

var output = {};

//Iterate over Dates
Object.keys(data).forEach((date) => {
  var dateObj = data[date];
  output[date] = {};

  //Iterate over Shifts
  Object.keys(dateObj).forEach((shift) => {
    var shiftObj = dateObj[shift];

    //Iterate over machines
    Object.keys(shiftObj).forEach((machine) => {
      //Initialize if the machine is not already iterated earlier.
      output[date][machine] = output[date][machine] || {};
      Object.keys(shiftObj[machine]).forEach((keyValue) => {

        if (!output[date][machine][keyValue]) {
          output[date][machine][keyValue] = 0;
        }

        output[date][machine][keyValue] += shiftObj[machine][keyValue];

      });
    });
  });
});


console.log(output);

Comments

0

Here's my version:

const data = {"28-11":{"S1":{"M1":{"K1":10,"K2":12,"K3":15},"M2":{"K1":8,"K2":6,"K3":5}},"S2":{"M1":{"K1":8,"K2":6,"K3":5},"M2":{"K1":10,"K2":12,"K3":15}}}};

var out = {};

Object.keys(data).forEach(date => 
    Object.keys(data[date]).forEach(shift => 
        Object.keys(data[date][shift]).forEach(machine => 
            Object.keys(data[date][shift][machine]).forEach(key => {
                if (out[date] === undefined) out[date] = {};
                if (out[date][machine] === undefined) out[date][machine] = {};
                if (out[date][machine][key] === undefined) out[date][machine][key] = 0;
                out[date][machine][key] += data[date][shift][machine][key];

            })
        )
   )
)
console.log(out)
return out;

Comments

0

Here's another possibility.

var data = {"28-11":{"S1":{"M1":{"K1":10,"K2":12,"K3":15},"M2":{"K1":8,"K2":6,"K3":5}},"S2":{"M1":{"K1":8,"K2":6,"K3":5},"M2":{"K1":10,"K2":12,"K3":15}}}};
    
    data = _.mapObject(data, function (days) {
      return _.map(days, function (day) {
        return _.reduce(day, function (acc, machine) {
          return _.extend({}, acc, _.mapObject(machine, function (keyVal, keyKey) {
            return (acc[keyKey] || 0) + keyVal;
          }));
        }, {});
      });
    });
    
    console.log(data);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

Here's the JSBin.

Honestly if you want to get good with maps and reduces and such I suggest you go through this tutorial webpage for RxJS. You can quit as soon as it starts using RxJS, but it teaches functional programming to start with and it teaches you to map filter and reduce like a boss.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.