Guy Messika
Guy Messika

Reputation: 169

how to calculate new data from objects in javascript

I have the following data:

[ {
  "names" : [ "a3","printed","black" ],
  "value" : 15
}, {
  "names" : [ "a3","copied","black" ],
  "value" : 87
}, {
  "names" : [ "a3","printed","color","full" ],
  "value" : 37
}, {
  "names" : [ "a3","copied","color","single" ],
  "value" : 0
}, {
  "names" : [ "a3","copied","color","full" ],
  "value" : 44
}, {
  "names" : [ "a3","scanned" ],
  "value" : 288
}, {
  "names" : [ "total" ],
  "value" : 242142
}, {
  "names" : [ "scanned" ],
  "value" : 67411
}, {
  "names" : [ "copied","black" ],
  "value" : 79997
}, {
  "names" : [ "copied","full","color" ],
  "value" : 809
}, {
  "names" : [ "copied","single","color" ],
  "value" : 0
}, {
  "names" : [ "printed","two","color" ],
  "value" : 0
}, {
  "names" : [ "printed","black" ],
  "value" : 120665
}, {
  "names" : [ "printed","full","color" ],
  "value" : 40657
} ]

I tried to create some structure to organize the data in a way I can see relations between objects and calculate new objects. basically what I want is to be able to calculate missing data. So for example I know these relations:

{
  "colors" : {
    "black" : "",
    "color" : [ "full", "two", "single" ]
  },
  "functions" : {
    "scanned" : "",
    "total" : [ "printed", "copied", "faxed" ]
  },
  "papers" : {
    "a3" : ""
  }
}

Based on this I would like to get the following:

{
    "a3" : 183,
    "color" : 41466,
    "black" : 200662,
    "copied" : 80806,
    "printed" : 161322
}

I know it by taking into consideration the following: a3 total is only composed of the functions printed, copied and faxed so for example the a3 scanned value is not inside that calculation for the value of a3 total.

but I can't think of any idea how to do it using JavaScript. can anybody points me in the right direction?

Upvotes: 2

Views: 2103

Answers (3)

Nina Scholz
Nina Scholz

Reputation: 386654

Basically this proposal uses a tree for the wanted values.

  1. Generate a sort pattern for the right assignment of names property.

  2. Iterate the given data

    1. Get a copy of a.names.
    2. Sort names.
    3. Test if relations.functions.total contains the first element of names, then unshift 'total' to names.
    4. Iterate names and build an object based on the elements.
    5. Assign value to the value property in the object.
  3. Calculate all missing values only for result.total branch.

    1. This sums all single properties as well for the wanted items.

function calculateValues(o) {
    return Object.keys(o).reduce(function (r, k) {
        var v;
        if (k === 'value') {
            return r;
        }
        v = calculateValues(o[k]);
        if (o[k].value === null) {
            o[k].value = v;
        }
        values[k] = (values[k] || 0) + o[k].value;
        return r + o[k].value;
    }, 0);
}

var data = [{ names: ["a3", "printed", "black"], value: 15 }, { names: ["a3", "copied", "black"], value: 87 }, { names: ["a3", "printed", "color", "full"], value: 37 }, { names: ["a3", "copied", "color", "single"], value: 0 }, { names: ["a3", "copied", "color", "full"], value: 44 }, { names: ["a3", "scanned"], value: 288 }, { names: ["total"], value: 242142 }, { names: ["scanned"], value: 67411 }, { names: ["copied", "black"], value: 79997 }, { names: ["copied", "full", "color"], value: 809 }, { names: ["copied", "single", "color"], value: 0 }, { names: ["printed", "two", "color"], value: 0 }, { names: ["printed", "black"], value: 120665 }, { names: ["printed", "full", "color"], value: 40657 }],
    relations = { colors: { "black": "", color: ["full", "two", "single"] }, functions: { scanned: "", total: ["printed", "copied", "faxed"] }, papers: { "a3": "" } },
    priorities = ['functions', 'colors', 'papers'], // as long as keys of objects are not ordered
    order = {},
    result = {},
    values = {},
    i = 0;

priorities.forEach(function (p) {
    Object.keys(relations[p]).forEach(function (k) {
        order[k] = ++i;
        Array.isArray(relations[p][k]) && relations[p][k].forEach(function (a) {
            order[a] = ++i;
        });
    });
});

data.forEach(function (a) {
    var names = a.names.slice();
    names.sort(function (a, b) {
        return (order[a] || 0) - (order[b] || 0);
    });
    if (relations.functions.total.indexOf(names[0]) !== -1) {
        names.unshift('total');
    }
    names.reduce(function (o, k) {
        return o[k] = o[k] || { value: null };
    }, result).value = a.value;
});

calculateValues(result.total);
// calculateCount(result.scanned); 

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

Upvotes: 22

Anton Chukanov
Anton Chukanov

Reputation: 655

Do you need to calculate value for each of names? If yes, then try

var output = {};
for (var i in data) {
    for (var j in data[i].names) {
        var mark = data[i].names[j];
        output[mark] = (output[mark] || 0) + data[i].value;
    }
} 

https://jsfiddle.net/chukanov/0kctjwyv/

Upvotes: 0

newDevGeek
newDevGeek

Reputation: 373

Below is the proof of concept. It is in CoffeeScript. You can easily compile it to JavaScript using js2.coffee. Else I have included the JS code for reference. Not sure if this is what you looking for. It probably isn't best approach but it may help you. Once you find (found), you can set your third (obj3) way you want.

###
THIS IS COFFEESCRIPT BELOW
###
Array::containsAny = (arr) ->
  @some (v) ->
    arr.indexOf(v) >= 0

obj1 = []
obj2 = {}
obj3 = {}
totalArr = []
colorArr = []
bl = null
scan = null
a3 = null

for k,v of obj2
  colObj = v if k is 'colors'
  funcObj = v if k is 'functions'
  paperObj = v if k is 'papers'
  if colObj isnt null
    for k,v of colObj
      colorArr = v if k is 'color'
      bl = k if k 'black'
  if funcObj isnt null
    for k,v of funcObj
      totalArr = v if k is 'total'
      scan = k if k is 'scanned'
  if paperObj isnt null
    for k,v of paperObj
      a3 = k if k is 'a3'
  return

for k,v of obj1
  names = v if k is 'names'
  val = v if k is 'value'
  foundBlack = names.containsAny(['black'])
  founda3 = names.containsAny(['a3'])
  foundColor = names.containsAny(colorArr)
  foundTotal = names.containsAny(TotalArr)
  return

var a3, bl, colObj, colorArr, foundBlack, foundColor, foundTotal, founda3, funcObj, k, names, obj1, obj2, obj3, paperObj, scan, totalArr, v, val;

Array.prototype.containsAny = function(arr) {
  return this.some(function(v) {
    return arr.indexOf(v) >= 0;
  });
};

//Your first array of objects
obj1 = [];

//your second object of objects
obj2 = {};

//declaring an empty object
obj3 = {};


totalArr = [];

colorArr = [];

bl = null;

scan = null;

a3 = null;

for (k in obj2) {
  v = obj2[k];
  if (k === 'colors') {
    colObj = v;
  }
  if (k === 'functions') {
    funcObj = v;
  }
  if (k === 'papers') {
    paperObj = v;
  }
  if (colObj !== null) {
    for (k in colObj) {
      v = colObj[k];
      if (k === 'color') {
        colorArr = v;
      }
      if (k('black')) {
        bl = k;
      }
    }
  }
  if (funcObj !== null) {
    for (k in funcObj) {
      v = funcObj[k];
      if (k === 'total') {
        totalArr = v;
      }
      if (k === 'scanned') {
        scan = k;
      }
    }
  }
  if (paperObj !== null) {
    for (k in paperObj) {
      v = paperObj[k];
      if (k === 'a3') {
        a3 = k;
      }
    }
  }
  return;
}

for (k in obj1) {
  v = obj1[k];
  if (k === 'names') {
    names = v;
  }
  if (k === 'value') {
    val = v;
  }
  foundBlack = names.containsAny(['black']);
  founda3 = names.containsAny(['a3']);
  foundColor = names.containsAny(colorArr);
  foundTotal = names.containsAny(TotalArr);
  return;
}

Upvotes: 0

Related Questions