3

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.

Adnan Sharif
  • 919
  • 7
  • 17
  • 2
    Maybe the sorting is slow because [you're using a wrong comparison function](https://stackoverflow.com/q/24080785/1048572). Try `return valA.a - valB.a || valA.b - valB.b || valA.c - valB.c || valA.d - valB.d;` – Bergi Mar 26 '19 at 19:17
  • This provides a clear in/output, an approach, a description what didn't work as expected ... why the downvote? – Jonas Wilms Mar 26 '19 at 19:17
  • Do you expect the output array to be sorted? – Bergi Mar 26 '19 at 19:18
  • @Bergi some of my values can be `null`. If so, will the comparison you have shown work? I will read the link you have shared later. And I don't expect the output to be sorted. – Adnan Sharif Mar 26 '19 at 19:26
  • 1
    @AdnanSharif `null` being used in a subtraction will be coerced to `0`. – Bergi Mar 26 '19 at 19:44

4 Answers4

5

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)).

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
2

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; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
1

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);
Bibberty
  • 4,670
  • 2
  • 8
  • 23
1

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};
    })
);
3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
  • 1
    Making heavy use of [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment). – 3limin4t0r Mar 26 '19 at 21:00