Underscore: sortBy() based on multiple attributes - javascript

I am trying to sort an array with objects based on multiple attributes. I.e if the first attribute is the same between two objects a second attribute should be used to comapare the two objects. For example, consider the following array:
var patients = [
[{name: 'John', roomNumber: 1, bedNumber: 1}],
[{name: 'Lisa', roomNumber: 1, bedNumber: 2}],
[{name: 'Chris', roomNumber: 2, bedNumber: 1}],
[{name: 'Omar', roomNumber: 3, bedNumber: 1}]
];
Sorting these by the roomNumber attribute i would use the following code:
var sortedArray = _.sortBy(patients, function(patient) {
return patient[0].roomNumber;
});
This works fine, but how do i proceed so that 'John' and 'Lisa' will be sorted properly?

sortBy says that it is a stable sort algorithm so you should be able to sort by your second property first, then sort again by your first property, like this:
var sortedArray = _(patients).chain().sortBy(function(patient) {
return patient[0].name;
}).sortBy(function(patient) {
return patient[0].roomNumber;
}).value();
When the second sortBy finds that John and Lisa have the same room number it will keep them in the order it found them, which the first sortBy set to "Lisa, John".

Here's a hacky trick I sometimes use in these cases: combine the properties in such a way that the result will be sortable:
var sortedArray = _.sortBy(patients, function(patient) {
return [patient[0].roomNumber, patient[0].name].join("_");
});
However, as I said, that's pretty hacky. To do this properly you'd probably want to actually use the core JavaScript sort method:
patients.sort(function(x, y) {
var roomX = x[0].roomNumber;
var roomY = y[0].roomNumber;
if (roomX !== roomY) {
return compare(roomX, roomY);
}
return compare(x[0].name, y[0].name);
});
// General comparison function for convenience
function compare(x, y) {
if (x === y) {
return 0;
}
return x > y ? 1 : -1;
}
Of course, this will sort your array in place. If you want a sorted copy (like _.sortBy would give you), clone the array first:
function sortOutOfPlace(sequence, sorter) {
var copy = _.clone(sequence);
copy.sort(sorter);
return copy;
}
Out of boredom, I just wrote a general solution (to sort by any arbitrary number of keys) for this as well: have a look.

I know I'm late to the party, but I wanted to add this for those in need of a clean-er and quick-er solution that those already suggested. You can chain sortBy calls in order of least important property to most important property. In the code below I create a new array of patients sorted by Name within RoomNumber from the original array called patients.
var sortedPatients = _.chain(patients)
.sortBy('Name')
.sortBy('RoomNumber')
.value();

btw your initializer for patients is a bit weird, isn't it?
why don't you initialize this variable as this -as a true array of objects-you can do it using _.flatten() and not as an array of arrays of single object, maybe it's typo issue):
var patients = [
{name: 'Omar', roomNumber: 3, bedNumber: 1},
{name: 'John', roomNumber: 1, bedNumber: 1},
{name: 'Chris', roomNumber: 2, bedNumber: 1},
{name: 'Lisa', roomNumber: 1, bedNumber: 2},
{name: 'Kiko', roomNumber: 1, bedNumber: 2}
];
I sorted the list differently and add Kiko into Lisa's bed; just for fun and see what changes would be done...
var sorted = _(patients).sortBy(
function(patient){
return [patient.roomNumber, patient.bedNumber, patient.name];
});
inspect sorted and you'll see this
[
{bedNumber: 1, name: "John", roomNumber: 1},
{bedNumber: 2, name: "Kiko", roomNumber: 1},
{bedNumber: 2, name: "Lisa", roomNumber: 1},
{bedNumber: 1, name: "Chris", roomNumber: 2},
{bedNumber: 1, name: "Omar", roomNumber: 3}
]
so my answer is : use an array in your callback function
this is quite similar to Dan Tao's answer, I just forget the join (maybe because I removed the array of arrays of unique item :))
Using your data structure, then it would be :
var sorted = _(patients).chain()
.flatten()
.sortBy( function(patient){
return [patient.roomNumber,
patient.bedNumber,
patient.name];
})
.value();
and a testload would be interesting...

None of these answers are ideal as a general purpose method for using multiple fields in a sort. All of the approaches above are inefficient as they either require sorting the array multiple times (which, on a large enough list could slow things down a lot) or they generate huge amounts of garbage objects that the VM will need to cleanup (and ultimately slowing the program down).
Here's a solution that is fast, efficient, easily allows reverse sorting, and can be used with underscore or lodash, or directly with Array.sort
The most important part is the compositeComparator method, which takes an array of comparator functions and returns a new composite comparator function.
/**
* Chains a comparator function to another comparator
* and returns the result of the first comparator, unless
* the first comparator returns 0, in which case the
* result of the second comparator is used.
*/
function makeChainedComparator(first, next) {
return function(a, b) {
var result = first(a, b);
if (result !== 0) return result;
return next(a, b);
}
}
/**
* Given an array of comparators, returns a new comparator with
* descending priority such that
* the next comparator will only be used if the precending on returned
* 0 (ie, found the two objects to be equal)
*
* Allows multiple sorts to be used simply. For example,
* sort by column a, then sort by column b, then sort by column c
*/
function compositeComparator(comparators) {
return comparators.reduceRight(function(memo, comparator) {
return makeChainedComparator(comparator, memo);
});
}
You'll also need a comparator function for comparing the fields you wish to sort on. The naturalSort function will create a comparator given a particular field. Writing a comparator for reverse sorting is trivial too.
function naturalSort(field) {
return function(a, b) {
var c1 = a[field];
var c2 = b[field];
if (c1 > c2) return 1;
if (c1 < c2) return -1;
return 0;
}
}
(All the code so far is reusable and could be kept in utility module, for example)
Next, you need to create the composite comparator. For our example, it would look like this:
var cmp = compositeComparator([naturalSort('roomNumber'), naturalSort('name')]);
This will sort by room number, followed by name. Adding additional sort criteria is trivial and does not affect the performance of the sort.
var patients = [
{name: 'John', roomNumber: 3, bedNumber: 1},
{name: 'Omar', roomNumber: 2, bedNumber: 1},
{name: 'Lisa', roomNumber: 2, bedNumber: 2},
{name: 'Chris', roomNumber: 1, bedNumber: 1},
];
// Sort using the composite
patients.sort(cmp);
console.log(patients);
Returns the following
[ { name: 'Chris', roomNumber: 1, bedNumber: 1 },
{ name: 'Lisa', roomNumber: 2, bedNumber: 2 },
{ name: 'Omar', roomNumber: 2, bedNumber: 1 },
{ name: 'John', roomNumber: 3, bedNumber: 1 } ]
The reason I prefer this method is that it allows fast sorting on an arbitrary number of fields, does not generate a lot of garbage or perform string concatenation inside the sort and can easily be used so that some columns are reverse sorted while order columns use natural sort.

Simple Example from http://janetriley.net/2014/12/sort-on-multiple-keys-with-underscores-sortby.html (courtesy of #MikeDevenney)
Code
var FullySortedArray = _.sortBy(( _.sortBy(array, 'second')), 'first');
With Your Data
var FullySortedArray = _.sortBy(( _.sortBy(patients, 'roomNumber')), 'name');

Perhaps underscore.js or just Javascript engines are different now than when these answers were written, but I was able to solve this by just returning an array of the sort keys.
var input = [];
for (var i = 0; i < 20; ++i) {
input.push({
a: Math.round(100 * Math.random()),
b: Math.round(3 * Math.random())
})
}
var output = _.sortBy(input, function(o) {
return [o.b, o.a];
});
// output is now sorted by b ascending, a ascending
In action, please see this fiddle: https://jsfiddle.net/mikeular/xenu3u91/

Just return an array of properties you want to sort with:
ES6 Syntax
var sortedArray = _.sortBy(patients, patient => [patient[0].name, patient[1].roomNumber])
ES5 Syntax
var sortedArray = _.sortBy(patients, function(patient) {
return [patient[0].name, patient[1].roomNumber]
})
This does not have any side effects of converting a number to a string.

You could concatenate the properties you want to sort by in the iterator:
return [patient[0].roomNumber,patient[0].name].join('|');
or something equivalent.
NOTE: Since you are converting the numeric attribute roomNumber to a string, you would have to do something if you had room numbers > 10. Otherwise 11 will come before 2. You can pad with leading zeroes to solve the problem, i.e. 01 instead of 1.

I think you'd better use _.orderBy instead of sortBy:
_.orderBy(patients, ['name', 'roomNumber'], ['asc', 'desc'])

I don't think most of the answers really work, and certainly there is none that works and uses purely underscore at the same time.
This answer provides sorting for multiple columns, with the ability to reverse the sort order for some of them, all in one function.
It also builds on the final code step by step, so you may want to take the last code snippet:
I have used this for two columns only (first sort by a, then by b):
var array = [{a:1, b:1}, {a:1, b:0}, {a:2, b:2}, {a:1, b:3}];
_.chain(array)
.groupBy(function(i){ return i.a;})
.map(function(g){ return _.chain(g).sortBy(function(i){ return i.b;}).value(); })
.sortBy(function(i){ return i[0].a;})
.flatten()
.value();
Here is the result:
0: {a: 1, b: 0}
1: {a: 1, b: 1}
2: {a: 1, b: 3}
3: {a: 2, b: 2}
I am sure this can be generalized for more than two...
Another version that might be faster:
var array = [{a:1, b:1}, {a:1, b:0}, {a:2, b:2}, {a:1, b:3}];
_.chain(array)
.sortBy(function(i){ return i.a;})
.reduce(function(prev, i){
var ix = prev.length - 1;
if(!prev[ix] || prev[ix][0].a !== i.a) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function(i){ return _.chain(i).sortBy(function(j){ return j.b; }).value();})
.flatten()
.value();
And a parametrized version of it:
var array = [{a:1, b:1}, {a:1, b:0}, {a:2, b:2}, {a:1, b:3}];
function multiColumnSort(array, columnNames) {
var col0 = columnNames[0],
col1 = columnNames[1];
return _.chain(array)
.sortBy(function(i){ return i[col0];})
.reduce(function(prev, i){
var ix = prev.length - 1;
if(!prev[ix] || prev[ix][0][col0] !== i[col0]) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function(i){ return _.chain(i).sortBy(function(j){ return j[col1]; }).value();})
.flatten()
.value();
}
multiColumnSort(array, ['a', 'b']);
And a parametrized version for any number of columns (seems to work from a first test):
var array = [{a:1, b:1, c:9}, {a:1, b:1, c:3}, {a:2, b:2, c:10}, {a:1, b:3, c:0}];
function multiColumnSort(array, columnNames) {
if(!columnNames || !columnNames.length || array.length === 1) return array;
var col0 = columnNames[0];
if(columnNames.length == 1) return _.chain(array).sortBy(function(i){ return i[col0]; }).value();
return _.chain(array)
.sortBy(function(i){ return i[col0];})
.reduce(function(prev, i){
var ix = prev.length - 1;
if(!prev[ix] || prev[ix][0][col0] !== i[col0]) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function(i){ return multiColumnSort(i, _.rest(columnNames, 1));})
.flatten()
.value();
}
multiColumnSort(array, ['a', 'b', 'c']);
If you want to be able to reverse the column sorting too:
var array = [{a:1, b:1, c:9}, {a:1, b:1, c:3}, {a:2, b:2, c:10}, {a:1, b:3, c:0}];
function multiColumnSort(array, columnNames) {
if(!columnNames || !columnNames.length || array.length === 1) return array;
var col = columnNames[0],
isString = !!col.toLocaleLowerCase,
colName = isString ? col : col.name,
reverse = isString ? false : col.reverse,
multiplyWith = reverse ? -1 : +1;
if(columnNames.length == 1) return _.chain(array).sortBy(function(i){ return multiplyWith * i[colName]; }).value();
return _.chain(array)
.sortBy(function(i){ return multiplyWith * i[colName];})
.reduce(function(prev, i){
var ix = prev.length - 1;
if(!prev[ix] || prev[ix][0][colName] !== i[colName]) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function(i){ return multiColumnSort(i, _.rest(columnNames, 1));})
.flatten()
.value();
}
multiColumnSort(array, ['a', {name:'b', reverse:true}, 'c']);
To also support functions:
var array = [{a:1, b:1, c:9}, {a:1, b:1, c:3}, {a:2, b:2, c:10}, {a:1, b:3, c:0}];
function multiColumnSort(array, columnNames) {
if (!columnNames || !columnNames.length || array.length === 1) return array;
var col = columnNames[0],
isString = !!col.toLocaleLowerCase,
isFun = typeof (col) === 'function',
colName = isString ? col : col.name,
reverse = isString || isFun ? false : col.reverse,
multiplyWith = reverse ? -1 : +1,
sortFunc = isFun ? col : function (i) { return multiplyWith * i[colName]; };
if (columnNames.length == 1) return _.chain(array).sortBy(sortFunc).value();
return _.chain(array)
.sortBy(sortFunc)
.reduce(function (prev, i) {
var ix = prev.length - 1;
if (!prev[ix] || (isFun ? sortFunc(prev[ix][0]) !== sortFunc(i) : prev[ix][0][colName] !== i[colName])) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function (i) { return multiColumnSort(i, _.rest(columnNames, 1)); })
.flatten()
.value();
}
multiColumnSort(array, ['a', {name:'b', reverse:true}, function(i){ return -i.c; }]);

If you happen to be using Angular, you can use its number filter in the html file rather than adding any JS or CSS handlers. For example:
No fractions: <span>{{val | number:0}}</span><br>
In that example, if val = 1234567, it will be displayed as
No fractions: 1,234,567
Example and further guidance at:
https://docs.angularjs.org/api/ng/filter/number

Related

Top highest values in an object (more if there are more max values and they are the same)

Lets suppose I have object like this:
var obj = {a : 5, b : 10, c : 15, d : 20, e : 20, f : 25};
I would like to get top 3 highest values - notice that d and e key have the same value and I need to get the keys also, so it would looks like:
Highest values:
f - 25
d - 20
e - 20
also if there are for example six values and four are identical:
var obj2 = {a:1, b:1, c:1, d:1, e:0,8, f: 0,5};
I need to show 4 highest.
Highest values:
a-1
b-1
c-1
d-1
I guess there is need to iterate over ALL object properties to get Math.max, but I also need a something to count 3 max numbers WITH their keys, and if there is more max (all the same) I need to "get them all!".
EDIT: there are great answers atm, so I guess I will not finish this code and just use given examples :)
This is an example implementation, with annotations to explain what is happening at each step.
function maxValues(o, n) {
// Get object values and sort descending
const values = Object.values(o).sort((a, b) => b - a);
// Check if more values exist than number required
if (values.length <= n) return o;
// Find nth maximum value
const maxN = values[n - 1];
// Filter object to return only key/value pairs where value >= maxN
return Object.entries(o)
.reduce((o, [k, v]) => v >= maxN ? { ...o, [k]: v } : o, {});
}
const a = maxValues({
a: 5,
b: 10,
c: 15,
d: 20,
e: 20,
f: 25
}, 3);
console.log(a);
const b = maxValues({
a: 1,
b: 1,
c: 1,
d: 1,
e: 0.8,
f: 0.5
}, 3);
console.log(b);
const c = maxValues({
a: 5,
b: 10,
}, 3);
console.log(c);
The callback passed to the Array.prototype.reduce function can be expanded out to the following:
return Object.entries(o)
.reduce(function (obj, [key, value]) {
if (v >= maxN) {
return Object.assign(obj, {
[key]: value
});
} else {
return obj;
}
}, {});
Instead, I condensed it down using an Arrow Function Expression, ternary operator, and spread syntax.
The ternary operator is essentially shorthand for an if/else statement. E.g.
condition ? true : false;
// or
v >= maxN ? { ...o, [k]: v } : o;
The spread syntax allows an iterable value to be expanded in place. In this instance, it's being used to copy existing key/value pairs from one object literal to another.
const a = { first_name: 'Rob', gender: 'male' };
const b = { ...a, username: 'fubar' };
console.log(b); // { first_name: 'Rob', gender: 'male', username: 'fubar' };
Simply,
Sort the object based on its values using, Object.entries
Get the least value you can filter.
Filter the entries and return as Object using Object.fromEntries.
function getTopValues(obj, topN)
{
var sortedEntries = Object.entries(obj).sort(function(a,b){return b[1]-a[1]});
var last = sortedEntries[topN-1][1];
var result = sortedEntries.filter(function(entry){
return entry[1] >= last;
});
console.log(Object.fromEntries(result));
}
getTopValues({a:5, b:10, c:15, d:20, e:20, f:25}, 3);
getTopValues({a:1, b:1, c:1, d:1, e:0.8, f: 0.5}, 3);
getTopValues({a:1, b:1, c:1, d:1, e:0.8, f: 0.5}, 5);
So, you want to find the top 3 highest and if there are multiple identical highest then you want to include all of that.
This problem is asked in a slightly weird fashion.
I am going to assume that if there is something like a:1 b:1 c:2 d:2 e:3,
you would like to include a,b,c and d.
First of all, you only have to keep track of the keys because you can get the values instantly at the end.
Ok! Let's start. (efficient but ugly)
class Numandamount {
constructor(number, amount) {
this.number = number;
this.amount = amount;
}
}
//That's just a class to link numbers and their amounts
var numtoamount = [];
//Now let's fill that array!
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
var num = obj.property;
var found = false;
for(Numandamount naa in numtoamount){
if(naa.number == num){
naa.amount++;
found = true;
}
}
if(!found){
naa.push(new Numandamount(num,1));
}
}
}
//The array is done!
numtoamount.sort(function(a,b){return b.number-a.number});
//Now all we have to do is loop through it!
var count = 0; // keep track of how many we did
var i = 0;
while(count<4 && i<numtoarray.length){
count += numtoamount[i].amount;
i++;
}
//BOOOM WE DID IT
// I is the biggest index so all we have to do is:
for(var j = 0;j<i;j++){
console.log("We have "+numtoamount[j].amount+" "+numtoamount[j].number+"'s");
}
For eg. it will print out for this example obj: {a:1 b:1 c:4 d:6 e:7 f:4}
We have 1 7's
We have 1 6's
We have 2 4's
If you would like some other implementation please comment below!
I put my heart into this <3
I would start with transforming your object into an array of objects:
const arr = []
for (var key in obj){
arr.push( {[key]: obj[key]} )
}
Now you have an array that looks like this:
[
{"f": 25},
{"d": 20},
{"e": 20},
{"c": 15},
{"b": 10},
{"a": 5}
]
Now you can sort your objects by the magnitude of their values:
const sortedArray = arr.sort( (a,b) => {
if (Object.values(a)[0] > Object.values(b)[0]) {
return -1
}
})
Which would give:
[
{"f": 25},
{"d": 20},
{"e": 20},
{"c": 15},
{"b": 10},
{"a": 5}
]
Then you can just pick however many values off the top you want. For example
sortedArray.filter( (item, index) => {
if (index <= 2 || Object.values(item)[0] === Object.values(sortedArray[0])[0]) {
return item
}
})
Which gives:
[
{"f": 25},
{"d": 20},
{"e": 20}
]
Or in the case of your second object, it would match the n highest values, but also grab any other values that are equal to the highest value.
As a single function:
function sortYourObject(object, number){
var arr = []
for (var key in object){
arr.push( {[key]: object[key]} )
}
const sortedArray = arr.sort( (a,b) => {
if (Object.values(a)[0] > Object.values(b)[0]) {
return -1
}
})
const endresult = sortedArray.filter( (item, index) => {
if (index <= 2 || Object.values(item)[0] === Object.values(sortedArray[0])[0]) {
return item
}
})
return endresult
}

Special sorting of an array of objects

So theres tons of posts on sorting something like this:
var arr = [{a: 1}, {b: 2}] alphabetically by key, but what if you have something like var arr = [{a: 100}, {a: 50}], what I want is to then say "oh you're the same? lets sort you then by value (which will always be a number).
I am unsure how to do either in lodash or any other similar javascript way.
The end result should be either:
[{b: 2}, {a: 1}] // Keys are different (alphabetical)
// or:
[{a: 50}, {a: 100}] // Keys are the same (lowest to highest)
Everything I have seen on stack overflow becomes a quick mess (code wise), and I was thinking, there are sort methods on lodash, but how exactly do I achieve what I want given the circumstances ??
Any ideas?
Some one asked a good question, are there more keys? Are they only a and b?
There would only be two objects in this array at any given time and yes the keys would only ever be strings, in this case the strings could be anything, cat, dog, mouse, apple, banana ... What ever.
The values will only ever be numbers.
Clearing the air
If the keys match, only sort the array by value, if the keys do not match, only sort the array by key. There will only ever be two objects in this array. Apologies for the misunderstanding.
You can use only one function to perform the two types of sorting (works for your case, in which you have only an array with two items, but it is completely generic regarding the array length):
var arr1 = [{a: 30}, {a: 2}];
var arr2 = [{b: 30}, {a: 2}];
function sortArr(arr) {
return arr.sort((a, b) => {
var aKey = Object.keys(a)[0];
var bKey = Object.keys(b)[0];
return (aKey !== bKey) ? aKey.localeCompare(bKey) : a[aKey] - b[bKey];
});
}
var sortedArr1 = sortArr(arr1);
var sortedArr2 = sortArr(arr2);
console.log(sortedArr1);
console.log(sortedArr2);
In case you always have one property in your objects you can first sort by key using localeCompare and then by value of that property.
var arr = [{b: 2}, {b: 10}, {a: 1}, {c: 1}, {a: 20}]
arr.sort(function(a, b) {
var kA = Object.keys(a)[0]
var kB = Object.keys(b)[0]
return kA.localeCompare(kB) || a[kA] - b[kB]
})
console.log(arr)
Before sorting you can create array of unique keys that you can use to check if all object have the same key by checking if length is > 1 and use that in sort function.
var arr = [{b: 10}, {b: 2}, {a: 1}, {c: 1}, {a: 20}, {b: 22}]
var arr2 = [{a: 10}, {a: 2}, {a: 1}, {a: 22}]
function customSort(data) {
var keys = [...new Set([].concat(...data.map(e => Object.keys(e))))]
data.sort(function(a, b) {
var kA = Object.keys(a)[0]
var kB = Object.keys(b)[0]
return keys.length > 1 ? kA.localeCompare(kB) : a[kA] - b[kB]
})
return data;
}
console.log(customSort(arr))
console.log(customSort(arr2))
var arr = [{b: 2}, {a: 1}, {b: 1}, {a: 100}, {a: 20}];
arr.sort(function(a, b) {
var aKey = a.hasOwnProperty("a")? "a": "b", // get the first object key (if it has the property "a" then its key is "a", otherwise it's "b")
bKey = b.hasOwnProperty("a")? "a": "b"; // same for the second object
return aKey === bKey? // if the keys are equal
a[aKey] - b[aKey]: // then sort the two objects by the value of that key (stored in either aKey or bKey)
aKey.localeCompare(bKey); // otherwise sort by the strings aKey and bKey (the keys of the two objects)
});
console.log(arr);

Getting key with the highest value from object

I have a object like that one:
Object {a: 1, b: 2, undefined: 1}
How can I quickly pull the largest value identifier (here: b) from it? I tried converting it to array and then sorting, but it didn't work out, since it got sorted alphabetically (and it seems like a overkill to juggle data back and forth just for getting one value out of three).
For example:
var obj = {a: 1, b: 2, undefined: 1};
Object.keys(obj).reduce(function(a, b){ return obj[a] > obj[b] ? a : b });
In ES6:
var obj = {a: 1, b: 2, undefined: 1};
Object.keys(obj).reduce((a, b) => obj[a] > obj[b] ? a : b);
Using Underscore or Lo-Dash:
var maxKey = _.max(Object.keys(obj), function (o) { return obj[o]; });
With ES6 Arrow Functions:
var maxKey = _.max(Object.keys(obj), o => obj[o]);
jsFiddle demo
Here is a suggestion in case you have many equal values and not only one maximum:
const getMax = object => {
return Object.keys(object).filter(x => {
return object[x] == Math.max.apply(null,
Object.values(object));
});
};
This returns an array, with the keys for all of them with the maximum value, in case there are some that have equal values.
For example: if
const obj = {apples: 1, bananas: 1, pears: 1 }
//This will return ['apples', 'bananas', 'pears']
If on the other hand there is a maximum:
const obj = {apples: 1, bananas: 2, pears: 1 }; //This will return ['bananas']
---> To get the string out of the array: ['bananas'][0] //returns 'bananas'`
Supposing you've an Object like this:
var obj = {a: 1, b: 2, undefined: 1}
You can do this
var max = Math.max.apply(null,Object.keys(obj).map(function(x){ return obj[x] }));
console.log(Object.keys(obj).filter(function(x){ return obj[x] == max; })[0]);
{a: 1, b: 2, undefined: 1}
The best work around I've seen is this
const chars = {a: 1, b: 2, undefined: 1}
//set maximum value to 0 and maxKey to an empty string
let max = 0;
let maxKey = "";
for(let char in chars){
if(chars[char]> max){
max = chars[char];
maxKey= char
}
}
console.log(maxKey)
Very basic method. might be slow to process
var v = {a: 1, b: 2, undefined: 1};
function geth(o){
var vals = [];
for(var i in o){
vals.push(o[i]);
}
var max = Math.max.apply(null, vals);
for(var i in o){
if(o[i] == max){
return i;
}
}
}
console.log(geth(v));
Combination of some ideas from other answers. This will get all the keys with the highest value, but using the spread operator to get the maximum value and then filter array method:
const getMax = object => {
let max = Math.max(...Object.values(object))
return Object.keys(object).filter(key => object[key]==max)
}
let obj = {a: 12, b: 11, c: 12};
getMax(obj)
let data = {a:1,b:2,undefined:3}
let maxValue = Object.entries(data).sort((x,y)=>y[1]-x[1])[0]
note: this is a very expensive process and would block the event loop if used with objects of large sizes(>=1000000). with large array slice the entries and call the above method recurrently using setTimeout.
If you need to return an array from an object with the keys for all duplicate properties with the (same or equal) highest value, try this:
const getMax = Object.keys(object)
.filter(x => {
return object[x] == Math.max.apply(null,
Object.values(object));
})
var object = { orange: 3, blue: 3, green: 1}
console.log(getMax) // ['orange', 'blue']

How can I do an asc and desc sort using underscore.js?

I am currently using underscorejs for sort my json sorting. Now I have asked to do an ascending and descending sorting using underscore.js. I do not see anything regarding the same in the documentation. How can I achieve this?
You can use .sortBy, it will always return an ascending list:
_.sortBy([2, 3, 1], function(num) {
return num;
}); // [1, 2, 3]
But you can use the .reverse method to get it descending:
var array = _.sortBy([2, 3, 1], function(num) {
return num;
});
console.log(array); // [1, 2, 3]
console.log(array.reverse()); // [3, 2, 1]
Or when dealing with numbers add a negative sign to the return to descend the list:
_.sortBy([-3, -2, 2, 3, 1, 0, -1], function(num) {
return -num;
}); // [3, 2, 1, 0, -1, -2, -3]
Under the hood .sortBy uses the built in .sort([handler]):
// Default is alphanumeric ascending:
[2, 3, 1].sort(); // [1, 2, 3]
// But can be descending if you provide a sort handler:
[2, 3, 1].sort(function(a, b) {
// a = current item in array
// b = next item in array
return b - a;
});
Descending order using underscore can be done by multiplying the return value by -1.
//Ascending Order:
_.sortBy([2, 3, 1], function(num){
return num;
}); // [1, 2, 3]
//Descending Order:
_.sortBy([2, 3, 1], function(num){
return num * -1;
}); // [3, 2, 1]
If you're sorting by strings not numbers, you can use the charCodeAt() method to get the unicode value.
//Descending Order Strings:
_.sortBy(['a', 'b', 'c'], function(s){
return s.charCodeAt() * -1;
});
The Array prototype's reverse method modifies the array and returns a reference to it, which means you can do this:
var sortedAsc = _.sortBy(collection, 'propertyName');
var sortedDesc = _.sortBy(collection, 'propertyName').reverse();
Also, the underscore documentation reads:
In addition, the Array prototype's methods are proxied through the chained Underscore object, so you can slip a reverse or a push into your chain, and continue to modify the array.
which means you can also use .reverse() while chaining:
var sortedDescAndFiltered = _.chain(collection)
.sortBy('propertyName')
.reverse()
.filter(_.property('isGood'))
.value();
Similar to Underscore library there is another library called as 'lodash' that has one method "orderBy" which takes in the parameter to determine in which order to sort it. You can use it like
_.orderBy('collection', 'propertyName', 'desc')
For some reason, it's not documented on the website docs.
Underscore Mixins
Extending on #emil_lundberg's answer, you can also write a "mixin" if you're using Underscore to make a custom function for sorting if it's a kind of sorting you might repeat in an application somewhere.
For example, maybe you have a controller or view sorting results with sort order of "ASC" or "DESC", and you want to toggle between that sort, you could do something like this:
Mixin.js
_.mixin({
sortByOrder: function(stooges, prop, order) {
if (String(order) === "desc") {
return _.sortBy(stooges, prop).reverse();
} else if (String(order) === "asc") {
return _.sortBy(stooges, prop);
} else {
return stooges;
}
}
})
Usage Example
var sort_order = "asc";
var stooges = [
{name: 'moe', age: 40},
{name: 'larry', age: 50},
{name: 'curly', age: 60},
{name: 'July', age: 35},
{name: 'mel', age: 38}
];
_.mixin({
sortByOrder: function(stooges, prop, order) {
if (String(order) === "desc") {
return _.sortBy(stooges, prop).reverse();
} else if (String(order) === "asc") {
return _.sortBy(stooges, prop);
} else {
return stooges;
}
}
})
// find elements
var banner = $("#banner-message");
var sort_name_btn = $("button.sort-name");
var sort_age_btn = $("button.sort-age");
function showSortedResults(results, sort_order, prop) {
banner.empty();
banner.append("<p>Sorting: " + prop + ', ' + sort_order + "</p><hr>")
_.each(results, function(r) {
banner.append('<li>' + r.name + ' is '+ r.age + ' years old.</li>');
})
}
// handle click and add class
sort_name_btn.on("click", function() {
sort_order = (sort_order === "asc") ? "desc" : "asc";
var sortedResults = _.sortByOrder(stooges, 'name', sort_order);
showSortedResults(sortedResults, sort_order, 'name');
})
sort_age_btn.on('click', function() {
sort_order = (sort_order === "asc") ? "desc" : "asc";
var sortedResults = _.sortByOrder(stooges, 'age', sort_order);
showSortedResults(sortedResults, sort_order, 'age');
})
Here's a JSFiddle demonstrating this: JSFiddle for SortBy Mixin
You can have reverse order in the first iteration:
_.sortBy([2, 3, 1], num => -num )

in javascript, is there an easy way to sort key-value pairs by the value, and return the key?

In javascript, is there an easy way to sort key-value pairs by the value (assume the value is numeric), and return the key? A jQuery way to do this would be useful as well.
(There are a lot of related questions about key-value pairs here, but I can't find one specifically about sorting.)
There's nothing easy to do this cross-browser. Assuming an array such as
var a = [
{key: "foo", value: 10},
{key: "bar", value: 1},
{key: "baz", value: 5}
];
... you can get an array of the key properties sorted by value as follows:
var sorted = a.slice(0).sort(function(a, b) {
return a.value - b.value;
});
var keys = [];
for (var i = 0, len = sorted.length; i < len; ++i) {
keys[i] = sorted[i].key;
}
// keys is ["bar", "baz", "foo"];
Let's assume we have an Array of Objects, like:
var data = [
{foo: 6},
{foo: 2},
{foo: 13},
{foo: 8}
];
We can call Array.prototype.sort()help, use Array.prototype.map()help to map a new array and Object.keys()help to grab the key:
var keys = data.sort(function(a,b) {
return a.foo - b.foo;
}).map(function(elem, index, arr) {
return Object.keys(elem)[0];
});
Be aware of, Array.prototype.map() requires Javascript 1.6 and Object.keys() is ECMAscript5 (requires Javascript 1.8.5).
You'll find alternative code for all those methods on MDC.
As far as I know, there isn't a built-in Javascript function to sort an array by its keys.
However, it shouldn't take too much code to do it: just extract the keys into their own array, sort them using the normal sort function, and rebuild the array in the right order. Something like this should do the trick:
function SortArrayByKeys(inputarray) {
var arraykeys=[];
for(var k in inputarray) {arraykeys.push(k);}
arraykeys.sort();
var outputarray=[];
for(var i=0; i<arraykeys.length; i++) {
outputarray[arraykeys[i]]=inputarray[arraykeys[i]];
}
return outputarray;
}
Now you can just call your function like so:
var myarray = {'eee':12, 'blah':34 'what'=>66, 'spoon':11, 'snarglies':22};
myarray = SortArrayByKeys(myarray);
And the output will be:
{'blah':34, 'eee':12, 'spoon':11, 'snarglies':22, 'what':66}
Hope that helps.
Working test page here: http://jsfiddle.net/6Ev3S/
Given
var object = {
'a': 5,
'b': 11,
'c': 1,
'd': 2,
'e': 6
}
You can sort object's keys by their values using the following:
Object.keys(object).sort(function (a, b) {
return object[a] - object[b]
}))
Result
[ 'c', 'd', 'a', 'e', 'b' ]
If you can't count on the exended array and object properties,
you can use the original Array methods-
function keysbyValue(O){
var A= [];
for(var p in O){
if(O.hasOwnProperty(p)) A.push([p, O[p]]);
}
A.sort(function(a, b){
var a1= a[1], b1= b[1];
return a1-b1;
});
for(var i= 0, L= A.length; i<L; i++){
A[i]= A[i][0];
}
return A;
}
//test
var Obj={a: 20, b: 2, c: 100, d: 10, e: -10};
keysbyValue(Obj)
/* returned value: (Array)
e,b,d,a,c
*/

Categories