Adnan Sharif
Adnan Sharif

Reputation: 967

Improve Array transformation in Javascript

Suppose I have a input array like the following

var inputArray = [
    {a: 1, b: 1, c: 1, d: 1, value: 1, rank: 1},
    {a: 1, b: 1, c: 1, d: 1, value: 2, rank: 2},
    {a: 1, b: 1, c: 1, d: 1, value: 3, rank: 3},
    {a: 1, b: 1, c: 1, d: 1, value: 4, rank: 4},
    {a: 1, b: 1, c: 1, d: 1, value: 5, rank: 5},
    {a: 1, b: 2, c: 1, d: 1, value: 1, rank: 1},
    {a: 1, b: 2, c: 1, d: 1, value: 2, rank: 2},
    {a: 1, b: 2, c: 1, d: 1, value: 3, rank: 3},
    {a: 1, b: 2, c: 1, d: 1, value: 4, rank: 4},
    {a: 1, b: 2, c: 1, d: 1, value: 5, rank: 5}
]

I want to transform my inputArray to the following outputArray

var outputArray = [
    {
        a: 1,
        b: 1,
        c: 1,
        d: 1,
        values:{
            "1":{value: 1},
            "2":{value: 2},
            "3":{value: 3},
            "4":{value: 4},
            "5":{value: 5}
        }
    },
    {
        a: 1,
        b: 2,
        c: 1,
        d: 1,
        values:{
            "1":{value: 1},
            "2":{value: 2},
            "3":{value: 3},
            "4":{value: 4},
            "5":{value: 5}
        }
    }
]

That means, I need create a dictionary for same property of a, b, c and d where the value of property rank is the key of the dictionary and the value of the dictionary is an object where the only property is value.

We assume that the inputArray will not be sorted with respect to the combination of a, b, c and d. So, my approach is like this,

(function(){
    var inputArray = [
        {a: 1, b: 1, c: 1, d: 1, value: 1, rank: 1},
        {a: 1, b: 1, c: 1, d: 1, value: 2, rank: 2},
        {a: 1, b: 1, c: 1, d: 1, value: 3, rank: 3},
        {a: 1, b: 1, c: 1, d: 1, value: 4, rank: 4},
        {a: 1, b: 1, c: 1, d: 1, value: 5, rank: 5},
        {a: 1, b: 2, c: 1, d: 1, value: 1, rank: 1},
        {a: 1, b: 2, c: 1, d: 1, value: 2, rank: 2},
        {a: 1, b: 2, c: 1, d: 1, value: 3, rank: 3},
        {a: 1, b: 2, c: 1, d: 1, value: 4, rank: 4},
        {a: 1, b: 2, c: 1, d: 1, value: 5, rank: 5}
    ]

    var temp = inputArray.sort(function(valA, valB){
        if(valA.a === valB.a){
            if(valA.b === valB.b){
                if(valA.c === valB.c){
                    return valA.d < valB.d;
                }
                return valA.c < valB.c;
            }
            return valA.b < valB.b;
        }
        return valA.a < valB.a;
    });

    var outputArray = [],
    currentIndex = 0;
    for(var i = 0; i < inputArray.length; i++){
        if(i > 0 && isConfigurationSame(inputArray[i], inputArray[i-1])){
            outputArray[currentIndex-1].values[inputArray[i].rank] = {
                value: inputArray[i].value
            }
        }
        else{
            outputArray.push(mapToOutputArrayObject(inputArray[i]));
            currentIndex++;
        }
    }
    console.log(outputArray);

    function isConfigurationSame(A, B) {
        return A.a === B.a
            && A.b === B.b
            && A.c === B.c
            && A.d === B.d;
    }

    function mapToOutputArrayObject(val){
        var row = {};
        row['a'] = val.a;
        row['b'] = val.b;
        row['c'] = val.c;
        row['d'] = val.d;
        row['values'] = {};
        row.values[val.rank] = {
            value: val.value
        }
        return row;
    }
}());

But the problem is, this thing is really getting more time if the length of input array is huge. This multi-criteria sorting also taking much time.

Is there any better approach to accomplish the result more efficiently with less time?

Thanks for your time and patience.

UPDATE: The values of a, b, c and d can be integer or null.

Upvotes: 3

Views: 147

Answers (4)

3limin4t0r
3limin4t0r

Reputation: 21120

Here is another option, first grouping by a, b, c and d. Then mapping over each group transforming the value and rank.

var inputArray = [{a: 1, b: 1, c: 1, d: 1, value: 1, rank: 1}, {a: 1, b: 1, c: 1, d: 1, value: 2, rank: 2}, {a: 1, b: 1, c: 1, d: 1, value: 3, rank: 3}, {a: 1, b: 1, c: 1, d: 1, value: 4, rank: 4}, {a: 1, b: 1, c: 1, d: 1, value: 5, rank: 5}, {a: 1, b: 2, c: 1, d: 1, value: 1, rank: 1}, {a: 1, b: 2, c: 1, d: 1, value: 2, rank: 2}, {a: 1, b: 2, c: 1, d: 1, value: 3, rank: 3}, {a: 1, b: 2, c: 1, d: 1, value: 4, rank: 4}, {a: 1, b: 2, c: 1, d: 1, value: 5, rank: 5}];

function groupBy(array, callback) {
  return array.reduce((groups, item, ...args) => {
    const key = callback(item, ...args),
          group = groups[key] || (groups[key] = []);

    group.push(item);
    return groups;
  }, {});
};

console.log(
  Object
    .values( groupBy(inputArray, ({a, b, c, d}) => [a, b, c, d]) )
    .map(group => {
      const {a, b, c, d} = group[0],
            values = {};
      
      group.forEach(({value, rank}) => values[rank] = {value});
      return {a, b, c, d, values};
    })
);

Upvotes: 1

Bibberty
Bibberty

Reputation: 4768

Here is a stab at it using Set, Map and a const method to build the Values object.

var inputArray = [
    {a: 1, b: 1, c: 1, d: 1, value: 1, rank: 1},
    {a: 1, b: 1, c: 1, d: 1, value: 2, rank: 2},
    {a: 1, b: 1, c: 1, d: 1, value: 3, rank: 3},
    {a: 1, b: 1, c: 1, d: 1, value: 4, rank: 4},
    {a: 1, b: 1, c: 1, d: 1, value: 5, rank: 5},
    {a: 1, b: 2, c: 1, d: 1, value: 1, rank: 1},
    {a: 1, b: 2, c: 1, d: 1, value: 2, rank: 2},
    {a: 1, b: 2, c: 1, d: 1, value: 3, rank: 3},
    {a: 1, b: 2, c: 1, d: 1, value: 4, rank: 4},
    {a: 1, b: 2, c: 1, d: 1, value: 5, rank: 5}
];

const getValueObject = (a,b,c,d, arr) => {
  let obj = {};
  arr.filter(i => i.a === a &&
                  i.b === b &&
                  i.c ===c &&
                  i.d === d)
    .forEach(item => obj[item.value] = item.rank);
  return obj;
};

// Get a set based on the key a,b,c,d
let newArray = [...new Set(inputArray.map(({a,b,c,d}) => `${a},${b},${c},${d}`))]
              .map(item => {
                let [a,b,c,d] = item.split(',').map(i => parseInt(i));
                // filter and add
                return {
                  a: a,
                  b: b,
                  c: c,
                  d: d,
                  values: getValueObject(a,b,c,d, inputArray)
                };
  
});

console.log(newArray);

Upvotes: 1

Nina Scholz
Nina Scholz

Reputation: 386568

You could take a Map and an array of the grouping keys and collect the values for each group.

var array = [{ a: 1, b: 1, c: 1, d: 1, value: 1, rank: 1 }, { a: 1, b: 1, c: 1, d: 1, value: 2, rank: 2 }, { a: 1, b: 1, c: 1, d: 1, value: 3, rank: 3 }, { a: 1, b: 1, c: 1, d: 1, value: 4, rank: 4 }, { a: 1, b: 1, c: 1, d: 1, value: 5, rank: 5 }, { a: 1, b: 2, c: 1, d: 1, value: 1, rank: 1 }, { a: 1, b: 2, c: 1, d: 1, value: 2, rank: 2 }, { a: 1, b: 2, c: 1, d: 1, value: 3, rank: 3 }, { a: 1, b: 2, c: 1, d: 1, value: 4, rank: 4 }, { a: 1, b: 2, c: 1, d: 1, value: 5, rank: 5 }],
    keys = ['a', 'b', 'c', 'd'],
    result = [],
    map = new Map;
    
array.forEach(o => {
    var key = keys.map(k => o[k]).join('|'),
        temp = map.get(key);

    if (!temp) {
        map.set(key, temp = Object.assign(...keys.map(k => ({ [k]: o[k] })), { values: {} }));
        result.push(temp);
    }

    temp.values[o.rank] = { value: o.value };
});

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

Upvotes: 2

Jonas Wilms
Jonas Wilms

Reputation: 138267

You could create a hashtable and generate a unique key based on a, b, c and d:

const hash = {};

for(const { a, b, c, d, value, rank } of array) {
  const key = JSON.stringify([a, b, c, d]); // generate a unique, but not random key
  if(hash[key]) { // check if it already exists,
   hash[key].values[rank] = value; // merge
  } else {
   hash[key] = { // create a new entry
     a, b, c, d,
     values: { [rank]: value },
   };
  }
}

const result = Object.values(hash); // turn the object into an array

That is O(n), which is better as the time complexity of any .sort implementation (but it only works if a, b, c and d are serializable (like in this case)).

Upvotes: 5

Related Questions