Frequency sorting array of objects in JS and if frequency Matches sort based on object property

I have an array of objects

[
    {"X" : {
        "price" : "5"
      }
    },
    {"Y" : {
        "price" : "3"
      }
    },
    {"Y" : {
        "price" : "3"
      }
    },
    {"Z" : {
        "price" : "4"
      }
    },
    {"Q" : {
        "price" : "2"
      }
    },
    {"X" : {
        "price" : "5"
      }
    },
    {"Z" : {
        "price" : "4"
      }
    },
    {"X" : {
        "price" : "5"
      }
    }
]

      

I want the frequency Sorting the array so that I get like [object:count]

How to get the array to convert arr to format:

// [{key: x, count: 3, price: 5}},{key: y:, count: 2, price: 3}

[{x:3},{y:2},{z:2},{q:1}]

      

But the problem I am facing is if the frequency is the same, then the sort should check the property of the object i: e in this case the price, and if the price is greater than the other corresponding element to which the weight should be given, so in this case the price z is greater than y, so z should be given priority.

[{x:3},{z:2},{y:2},{q:1}]

      

This is what I have tried so far:

var a = ["x", "v"], b = ["x", "y"], c = ["d", "y"];
var d = a.concat(b, c);
    
function sortByFrequency(array) {
    var frequency = {};
    
    array.forEach(function(value) { frequency[value] = 0; });
  
    var uniques = array.filter(function(value) {
        return ++frequency[value] == 1;
    });
    
    return uniques.sort(function(a, b) {
        return frequency[b] - frequency[a];
    });
}
    
var frequency = sortByFrequency(d);
    
console.log(frequency);
      

.as-console-wrapper{min-height:100%}
      

Run codeHide result


Update after answer

I still don't know how to convert the array to this format

   var arr = [
{"key":"X",
	"price" : "5",
	"count" :"3"
  }
,
{"key":"Y",
	"price" : "3",
	"count" :"2"
  }
,
{"key":"Z",
	"price" : "4",
	"count" : "2"
  }

];

var r = _.sortBy(_.sortBy(arr, 'price'), 'count'); 

console.log(JSON.stringify(r));
      

<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
      

Run codeHide result


now it works, but how to get object in this format from array

+3


source to share


4 answers


Try using this with reduce

, and then sort

.

Explaination

To achieve the desired result, you can complete the task below.

1. Grouping . Group the elements in the array using property names (x, y, z)
2. Sort . Sorting the result from step 1 in descending order, where the first criteria is the number of items and the second is the price.

1.Grouping - There is no built-in function in javascript group by

. Therefore, we can use a function reduce

that basically runs the function on the sequence of the array and also returns the accumulated value.

and. In the shrink function, the accumulator will start from an empty array, as indicated in the code comments at the end of the shrink function.

b. We get the property name as "x", "y", "z" by moving the object. In addition, we use the zero index because there is only one property like "x", "y", "z".



from. After that, we check if the property is in the array or not.

If the property is not in the array, then we need to add the property to the array.

f. We create an object to handle quantity and price information that will be used later.

f. If the property already exists as mentioned in step c, then we need to increment the counter of that propertyelementInArray[propName].count++;

2. Sorting
  a. The function sort

performs a comparison function. In this function, we compare 2 elements on our own count

. If count

equal, then we will compare them using price

.

var arr = [
    {"X" : {
        "price" : "5"
      }
    },
    {"Y" : {
        "price" : "3"
      }
    },
    {"Y" : {
        "price" : "3"
      }
    },
    {"Z" : {
        "price" : "4"
      }
    },
    {"Q" : {
        "price" : "2"
      }
    },
    {"X" : {
        "price" : "5"
      }
    },
    {"Z" : {
        "price" : "4"
      }
    },
    {"X" : {
        "price" : "5"
      }
    }
];

var frequency = arr.reduce(function (accumulatorObject, currentValue) { 
  var propName = Object.keys(currentValue)[0];
  var elementInArray = accumulatorObject.find((element) => Object.keys(element)[0] === propName);
  if (elementInArray) {
    elementInArray[propName].count++;
  }
  else {
    var newObject = {};
	newObject[propName]  = {};
	newObject[propName].count = 1;
	newObject[propName].price = +currentValue[propName].price;
	accumulatorObject.push(newObject);
 }
  return accumulatorObject;
}, []); //  // Accumulator starts with Empty array. 



frequency.sort(function(first,second){ 
    var diff =  second[Object.keys(second)].count - first[Object.keys(first)].count;
	if( diff === 0) {
		return second[Object.keys(second)].price - first[Object.keys(first)].price;
	}
return diff;});
console.log(frequency);
      

Run codeHide result


+1


source


You can do it with the following ES6 code:

function sortByFrequency(a) {
    return Array.from(
        a.reduce( (acc, o) => {
            const key = Object.keys(o)[0];
            const obj = acc.get(key) || Object.assign({ key, count: 0 }, o[key]);
            obj.count++;
            return acc.set(key, obj);
        }, new Map),
        ([key, obj]) => obj
    ).sort( (a, b) => b.count - a.count || b.price - a.price );
}

// Sample input
const a = [{
    X: {
        price: "5"
    }
}, {
    Y: {
        price: "3"
    }
}, {
    Y: {
        price: "3"
    }
}, {
    Z: {
        price: "4"
    }
}, {
    Q: {
        price: "2"
    }
}, {
    X: {
        price: "5"
    }
}, {
    Z: {
        price: "4"
    }
}, {
    X: {
        price: "5"
    }
}];

// Perform transformation & output
const res = sortByFrequency(a);
console.log(res);
      

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

Run codeHide result


Explanation

The code generates Map

to secure one entry per key. It is instantiated with reduce

, which is given as an initial value new Map

and then referenced as acc

.

reduce

will iterate over the input array a

, and for each entry, it will fetch key

with Object.keys

. Since there is (and should be) only one key for each object, it is retrieved using [0]

from the resulting array of keys.

It then acc.get

checks to see if we already have an entry for this key. If so, it is obj

set to the object that we previously saved for this key. If not - and this is the case in the first iteration - a new object is created with properties key

and count

that get the correct values, and this object is merged with the deeper object in the input array ( o[key]

). In practical terms, this means that the key and value price

are appended to an object that already has key

and count

.



In any case (we created a new object or retrieved it from the Map), its count property is incremented.

This object is then stored on the map in the appropriate key using acc.set(key, obj)

. This is returned in the reduce

internals (i.e. the updated is returned acc

) and this will be the value acc

in the next iteration, as it works reduce

.

After final iteration reduce

will return the completed map. It is then converted to an array with Array.from

. During its execution, we will convert each record, because by default the map record will be converted to a key / value pair (array), but we want to store the value (since it now contains a property key

). So this is what happens in the callback argument provided Array.from

:

([key, obj]) => obj

      

We now have an array of objects, where each object has three desired properties. All that remains is sorting.

To do this, we subtract the counts of the two objects that are compared (as you have already done). However, when they are equal, we need to do more. In this case, the difference is zero, which is false, and so with a boolean, ||

we are forcing JavaScript to evaluate what follows. In this case, we are sorting by price, again, subtracting prices from each other. Note that your prices are strings, but the subtraction operator converts them to numbers on the fly.

+2


source


I'm just giving you a skeleton, but a method _.sortBy

in lodash or underscore will do a stable view (i.e. keep previous sorts) and allow you to apply multiple views.

function sortByFrequency(arr) {
    // Transform arr to the format: 
    // [{key: x, count: 3, price: 5}},{key: y:, count: 2, price: 3}, ... 

    arr = _.sortBy(_.sortBy(arr, 'price'), 'count'); 
    // in lodash, this is arr = _.sortBy(arr, ['price', 'count'])

    // transform arr into the format that you want 
    return arr.map(x => /* function */)

}

      

0


source


Just an update on John's comment to Aghalo's answer

Mapping each of the object keys to a new object

In the Else block you can write this

 var newObject = {};  
    newObject[propName]  = {};
    newObject[propName].count = 1;
  Object.assign(newObject[propName], currentValue[propName]); // this line should do the trick also you need not convert proce to number as while sorting it is done at runtime .
    //newObject[propName].price = +currentValue[propName].price;
  accumulatorObject.push(newObject);

      

0


source







All Articles