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.

5
  • 2
    Maybe the sorting is slow because you're using a wrong comparison function. Try return valA.a - valB.a || valA.b - valB.b || valA.c - valB.c || valA.d - valB.d;
    – Bergi
    Commented Mar 26, 2019 at 19:17
  • This provides a clear in/output, an approach, a description what didn't work as expected ... why the downvote? Commented Mar 26, 2019 at 19:17
  • Do you expect the output array to be sorted?
    – Bergi
    Commented Mar 26, 2019 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. Commented Mar 26, 2019 at 19:26
  • 1
    @AdnanSharif null being used in a subtraction will be coerced to 0.
    – Bergi
    Commented Mar 26, 2019 at 19:44

4 Answers 4

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

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

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

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

1

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.