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%}
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>
now it works, but how to get object in this format from array
source to share
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);
source to share
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; }
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.
source to share
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 */)
}
source to share
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);
source to share