Reputation: 31
i'd like to sort an array by two fields while providing a desired number to sort them by. i tried using lodash but haven't got the desired results.
for example
const data = [
{name: 'first', score: 22, average: 59},
{name: 'second', score: 34, average: 83},
{name: 'third', score: 40, average: 24},
{name: 'fourth', score: 29, average: 49},
{name: 'fifth', score: 23, average: 55}
];
// call function like this
sortByTwoFields({field:'score', number:21}, {field:'average', number:50});
the desired result would be
const result = [
{name: 'fifth', score: 23, average: 55},
{name: 'fourth', score: 29, average: 49},
{name: 'first', score: 22, average: 59},
{name: 'third', score: 40, average: 24},
{name: 'second', score: 34, average: 83}
];
any ideas would be greatly appreciated
Upvotes: 3
Views: 135
Reputation: 67900
You can as easily make it generic by sorting by any number of fields, not just two:
function sortByFields(objs, fields) {
const diffToFields = obj =>
_(fields).map(f => Math.abs(obj[f] - f.number)).sum();
return _(objs).sortBy(diffToFields).value();
}
const sortedData = sortByFields(data, [
{field: 'score', number: 21},
{field: 'average', number: 50},
]);
Upvotes: 0
Reputation: 4175
How about simply use _.sortBy
of lodash
??
_.sortBy(data, [a=> Math.abs(a.score - 21), a=> Math.abs(a.average - 50)])
Isn't this enough?
EDIT
Well, Yes! if you need this sorting (execution) from one place and with only (exactly that 2) 2 nested fields, then this ole liner is ok, but if you are calling from several places and with diff fields (nested) with closest value, then you can wrap this in a function, prepare the itarees dynamically and pass it to sortBy
.
Here is a working example for that:
var data = [
{name: 'first', score: 22, average: 59},
{name: 'second', score: 34, average: 83},
{name: 'second', score: 34, average: 80},
{name: 'third', score: 40, average: 24},
{name: 'fourth', score: 29, average: 49},
{name: 'fifth', score: 23, average: 55}
];
function sortByClosest(arr, ...fields) {
//this will create array of callbacks, similar to what user in first example
let iteratees = fields.map(f => o => Math.abs(f.number - o[f.field]));
return _.sortBy(arr, iteratees);
}
//pass data for "arr" argument, and all other
//sort key/closest-value for fields array argument
var res = sortByClosest(data, {field:'score', number:21},{field:'average',number:50});
console.log(res);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
However key, closest value can be passes in more shorter format like
{score: 21, average: 50}
, in that case the logic to generate iteratees
needs to adjust accordingly, so it will became:
_.map(fields, (v,k)=> o=> Math.abs(v - o[k]))
And you need to call it with a single object (as shown above), here is the call for that:
sortByClosest(data, {score: 21, average: 50});
Upvotes: 1
Reputation: 1167
I don't believe your expected result is correct as the fourth has a smaller total difference.
Anyway, the function will work out the total difference between the values and the numbers given and then sort by that total.
function sortByTwoFields(data, field1, field2) {
data.sort(function (a, b) {
// Work out difference of two fields and add them together to get the total
var totalDifferenceA = Math.abs(field1.number - a[field1.field]) + Math.abs(field2.number - a[field2.field]);
var totalDifferenceB = Math.abs(field1.number - b[field1.field]) + Math.abs(field2.number - b[field2.field]);
// If a has a smaller total then b it should come first
return totalDifferenceA - totalDifferenceB;
});
}
const data = [
{name: 'first', score: 22, average: 59},
{name: 'second', score: 34, average: 83},
{name: 'third', score: 40, average: 24},
{name: 'fourth', score: 29, average: 49},
{name: 'fifth', score: 23, average: 55}
];
sortByTwoFields(data, {field: 'score', number: 21}, {field: 'average', number: 50});
console.log(data);
Upvotes: 0
Reputation: 35540
It appears that your desired result is slightly off as fourth
has a total difference of 9 while first
has a total difference of 10. The function below is called like sortByTwoFields(data, {field:'score', number:21}, {field:'average', number:50})
. You can add a weight
attribute to an object if you want to prioritize it, otherwise it defaults to 1.
function sortByTwoFields (data, info1, info2) {
if (! ("weight" in info1)) {
info1.weight = 1;
}
if (! ("weight" in info2)) {
info2.weight = 1;
}
return data.sort(function (a, b) {
var adiff1 = a[info1.field] - info1.number;
var adiff2 = a[info2.field] - info2.number;
var bdiff1 = b[info1.field] - info1.number;
var bdiff2 = b[info2.field] - info2.number;
return Math.abs(adiff1) * info1.weight + Math.abs(adiff2) * info2.weight - Math.abs(bdiff1) * info1.weight - Math.abs(bdiff2) * info2.weight;
});
}
Upvotes: 0