2

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?

5
  • Do you need to use the above two objects or would restructuring third one will be ok? Commented Oct 6, 2016 at 13:19
  • I must keep using the array of objects at the top of the question. other than that everything else can be changed Commented Oct 6, 2016 at 13:22
  • 2
    It's not very clear what you are needing... How are you generating the numbers for your required output? Not even sure how the "relations" data was used to make your required output Commented Oct 6, 2016 at 13:22
  • Based on what you're saying, you might look into something like the reduce() function on arrays. Reduce lets you transform data to something completely different. Your accumulator would simply be a hash with your desired keys. Commented Oct 6, 2016 at 13:43
  • 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. Commented Oct 6, 2016 at 15:04

3 Answers 3

22

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; }

Sign up to request clarification or add additional context in comments.

Comments

0

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;
}

Comments

0

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/

2 Comments

this is a good start but not every value for each of names need to be included in the calculation
I know my question is very unclear so I really liked the way you started it and keep going with this and getting close to solve my problem.

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.