Sort array on nested key - javascript

I am using this function to sort an array based on object key:
function keysrt(arr, key, reverse) {
var sortOrder = 1;
if(reverse){
sortOrder = -1;
}
return arr.sort(function(a, b) {
var x = a[key],
y = b[key];
return sortOrder * ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
It works well with this type of array, where key is on the first level:
var a = [
{ id: 0, last: 'Anne'},
{ id: 1, last: 'Odine'},
{ id: 2, last: 'Caroline'}
]
keysrt(a, 'last');
How can I make it work with this example, where title key is nested?
var b = [
{ id: 0, last: 'Anne', data:{title: 'habc'}},
{ id: 1, last: 'Odine', data:{title: 'asdf'}},
{ id: 2, last: 'Prentice', data:{title: 'tzuio'}}
]
keysrt(b, 'title');

For this idea the "key" variable changes into an array of keys: Then you specify the "path" to the nested value you want to sort on.
function keysrt(arr, keyArr, reverse) {
var sortOrder = 1;
if(reverse)sortOrder = -1;
return arr.sort(function(a, b) {
var x=a,y=b;
for (var i=0; i < keyArr.length; i++) {
x = x[keyArr[i]];
y = y[keyArr[i]];
}
return sortOrder * ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
keysrt(b,['data','title']);

If you are ready to change the function signature and the function call, here is a simple solution-
function keysrt(arr, prop, key, reverse) {
var sortOrder = 1;
if(reverse)sortOrder = -1;
return arr.sort(function(a, b) {
var x = a[prop][key]; var y = b[prop][key];
return sortOrder * ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
var b = [
{ id: 0, last: 'Anne', data:{title: 'habc'}},
{ id: 1, last: 'Odine', data:{title: 'asdf'}},
{ id: 2, last: 'Prentice', data:{title: 'tzuio'}}
]
keysrt(b,'data', 'title');
Here, prop represents the outer object, key would represent the nested key.
So, var y = b[prop][key] would basically mean you are accessing b.data.title
Hope it helps :) Happy coding!

If you need to make it generic, I think you can pass in a function that will retrieve the value from array item for comparison:
function keysrt(arr, reverse, getValueFn) {
var sortOrder = 1;
if(reverse)sortOrder = -1;
return arr.sort(function(a, b) {
var x = getValueFn(a); var y = getValueFn(b);
return sortOrder * ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
So that you can use it like:
keysrt(b, true, function(a){return a.data.title})

You can get working example with following code:
function keysrt(arr, key, reverse) {
var sortOrder = reverse ? -1 : 1;
return arr.sort(function(a, b) {
var x,y;
if(typeof a[key] !== "undefined") {
x = a[key];
y = b[key];
} else {
for(var prop in a) {
if(a[prop][key] !== "undefined") {
x = a[prop][key];
y = b[prop][key];
}
}
}
return sortOrder * ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
but I would propose more generic solution
function keysrt(arr, path, reverse) {
var sortOrder = reverse ? -1 : 1;
var pathSplitted = path.split(".");
if(arr.length <= 1) {
return arr;
}
return arr.sort(function(a, b) {
var x = a;
var y = b;
pathSplitted.forEach(function(key) {
x = x[key];
y = y[key];
});
return sortOrder * ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
in which one can provide a path to sorting field like this
var sorted = keysrt(b, 'data.title');
Demo: http://jsbin.com/cosugawoga/edit?js,console

To find a nested property value, any number of levels down, you can use JSON.stringify as a way to walk the object:
function get_nested_value(obj, prop) {
var result;
JSON.stringify(obj, function(key, value) {
if (key === prop) result = value;
});
return result;
}
Now:
function keysrt(arr, key, reverse) {
var sortOrder = 1;
if(reverse){
sortOrder = -1;
}
return arr.sort(function(a, b) {
var x = get_nested_value(a, key);
y = get_nested_value(b, key);
return sortOrder * ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}

Related

Sort javascript array null value always at bottom

Hey i am using slickgrid plugin and there i have function sortNumeric to sort data in order
function sorterNumeric(a, b) {
var x = (isNaN(a[sortcol]) || a[sortcol] === "" || a[sortcol] === null) ? -99e+10 : parseFloat(a[sortcol]);
var y = (isNaN(b[sortcol]) || b[sortcol] === "" || b[sortcol] === null) ? -99e+10 : parseFloat(b[sortcol]);
return sortdir * (x === y ? 0 : (x > y ? 1 : -1));
}
Can someone help me to extend this sorting, so null values comes always at last place.
You could use the result of the comparison as value for the needed delta.
In SlickGrid, you get the sort order with the property sortAsc of the wanted cols to sort. then just use the closure over the sorting direction.
function sortFn(sortAsc) {
return function (a, b) {
return (a[sortcol] === null) - (b[sortcol] === null) || (sortAsc || -1) * (a[sortcol] - b[sortcol]);
}
}
var array = [{ a: 1 }, { a: 3 }, { a: 2 }, { a: 8 }, { a: null }, { a: 42 }, { a: null }],
sortcol = 'a';
array.sort(sortFn(true)); // asc
console.log(array);
array.sort(sortFn(false)); // desc
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use following code:
var sortcol = "num";
var sortdir = -1;
function sorterNumeric(a, b) {
var x = (isNaN(a[sortcol]) || !a[sortcol]) ? 99e+10 * sortdir : parseFloat(a[sortcol]);
var y = (isNaN(b[sortcol]) || !b[sortcol]) ? 99e+10 * sortdir : parseFloat(b[sortcol]);
return x > y ? 1 * sortdir : -1 * sortdir;
}
var arr = [{ num: 1 }, { num: 3 }, { num: null }, { num: 7 }, { num: 2 } ]

Sorting nested array of objects with dynamic key

I am trying to create a sorting function that sorts a nested array of objects while giving a key dynamically (with different depths).
sortByKey(array, key){
var splitKey = key.split(".");
if(splitKey.length = 2){
return array.sort(function(a, b) {
var x = a[splitKey[0]][splitKey[1]]; var y = b[splitKey[0]][splitKey[1]];
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
} else {
return array.sort(function(a, b) {
var x = a[key]; var y = b[key];
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
}
I want to get rid of the if - else and use a for loop instead. The goal is that the function works with 'name', 'name.first' and 'name.first.another' (as an example). Is there a way to do this dynamically?
In other words, I want to use the same function with different arrays. So with one array I want to sort it calling sortByKey(array1, 'name') and with another sortByKey(array2, 'location.address') and maybe with a third sortByKey(array3, 'location.address.postalcode') or something like that.
Extract property extracting function
function prop(key) {
var keys = key.split('.');
return keys.reduce.bind(keys, function(obj, name) {
return obj[name]
})
}
and use it to well extract values :)
sortByKey(array, key){
var getKey = prop(key);
return array.sort(function(a, b){
var x = getKey(a); var y = getKey(b);
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
})
}
I think you mean something like this:
function sortByKey(array, key){
var splitKey = key.split(".");
return array.sort(function(a, b) {
var ta = a;
var tb = b;
for (var i=0; i<splitKey.length; i++) {
ta = ta[splitKey[i]];
};
/// return ((a < b) ? -1 : ((a > b) ? 1 : 0)); // Too complex ;-)
return a - b;
});
};
Your problem is a misused assignment, where it should be a comparison.
if (splitKey.length === 2) {
// ^^^
A shorter approach could use Array#reduce.
function sortByKey(array, key) {
var getValue = function (o, k) { return o[k]; },
keys = key.split(".");
return array.sort(function (a, b) {
return keys.reduce(getValue, a) - keys.reduce(getValue, b);
});
}
var array = [{ a: 5, b: { c: 2 } }, { a: 7, b: { c: 1 } }, { a: 1, b: { c: 3 } }];
sortByKey(array, 'a');
console.log(array);
sortByKey(array, 'b.c');
console.log(array);
ES6
function sortByKey(array, key) {
const getValue =
(keys => object => keys.reduce((o, k) => o[k], object))
(key.split('.'));
return array.sort((a, b) => getValue(a) - getValue(b));
}
var array = [{ a: 5, b: { c: 2 } }, { a: 7, b: { c: 1 } }, { a: 1, b: { c: 3 } }];
sortByKey(array, 'a');
console.log(array);
sortByKey(array, 'b.c');
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to get the highest key and value from object

I'm trying to get the highest key and value from an object, how can I return the desired result?
Here's my object:
categories = {
'personal' : 4,
'swag' : 3,
'mingle' : 2,
'attention' : 1
};
Desired functionality:
returnMax(categories) // {personal : 4}
Here is how I would do it: http://jsfiddle.net/nwj7sad1/5/
categories = {
'personal' : 4,
'swag' : 3,
'mingle' : 2,
'attention' : 1
};
console.log(MaxCat(categories));
function MaxCat(obj){
var highest = 0;
var arr = [];
for (var prop in obj) {
if( obj.hasOwnProperty( prop ) ) {
if(obj[prop] > highest ){
arr = [];
highest = obj[prop];
arr[prop] = highest;
}
}
}
return arr;
}
For these types of things I like to write my own algorythms. Here is one I quickly wrote up:
function highest(o){
var h = undefined;
for(var key in o){
var current = o[key];
if(h === undefined || current > h){
h = current;
}
}
return h;
}
JSFiddle

Sort array on key value

I have a function which sorts by name currently and an array of value / key pairs.
I wonder how can I pass the key on which sort is being performed so I can call the same function every time like so:
var arr = [{name:'bob', artist:'rudy'},
{name:'johhny', artist:'drusko'},
{name:'tiff', artist:'needell'},
{name:'top', artist:'gear'}];
sort(arr, 'name'); //trying to sort by name
sort(arr, 'artist'); //trying to sort by artist
function sort(arr) {
arr.sort(function(a, b) {
var nameA=a.name.toLowerCase(), nameB=b.name.toLowerCase();
if (nameA < nameB) //sort string ascending
return -1;
if (nameA > nameB)
return 1;
return 0; //default return value (no sorting)
});
}
Array.prototype.sortOn = function(key){
this.sort(function(a, b){
if(a[key] < b[key]){
return -1;
}else if(a[key] > b[key]){
return 1;
}
return 0;
});
}
var arr = [{name:'bob', artist:'rudy'},{name:'johhny', artist:'drusko'},{name:'tiff', artist:'needell'},{name:'top', artist:'gear'}];
arr.sortOn("name");
arr.sortOn("artist");
[edit 2020/08/14] This was rather an old answer and not very good as well, so simplified and revised.
Create a function that returns the sorting lambda (the Array.prototype.sort callback that does the actual sorting). That function can receive the key name, the kind of sorting (string (case sensitive or not) or numeric) and the sorting order (ascending/descending). The lambda uses the parameter values (closure) to determine how to sort.
const log = (...strs) =>
document.querySelector("pre").textContent += `\n${strs.join("\n")}`;
const showSortedValues = (arr, key) =>
` => ${arr.reduce((acc, val) => ([...acc, val[key]]), [])}`;
// the actual sort lamda factory function
const sortOnKey = (key, string, desc) => {
const caseInsensitive = string && string === "CI";
return (a, b) => {
a = caseInsensitive ? a[key].toLowerCase() : a[key];
b = caseInsensitive ? b[key].toLowerCase() : b[key];
if (string) {
return desc ? b.localeCompare(a) : a.localeCompare(b);
}
return desc ? b - a : a - b;
}
};
// a few examples
const onNameStringAscendingCaseSensitive =
getTestArray().sort( sortOnKey("name", true) );
const onNameStringAscendingCaseInsensitive =
getTestArray().sort( sortOnKey("name", "CI", true) );
const onValueNumericDescending =
getTestArray().sort( sortOnKey("value", false, true) );
// examples
log(`*key = name, string ascending case sensitive`,
showSortedValues(onNameStringAscendingCaseSensitive, "name")
);
log(`\n*key = name, string descending case insensitive`,
showSortedValues(onNameStringAscendingCaseInsensitive, "name")
);
log(`\n*key = value, numeric desc`,
showSortedValues(onValueNumericDescending, "value")
);
function getTestArray() {
return [{
name: 'Bob',
artist: 'Rudy',
value: 23,
}, {
name: 'John',
artist: 'Drusko',
value: 123,
}, {
name: 'Tiff',
artist: 'Needell',
value: 1123,
}, {
name: 'Top',
artist: 'Gear',
value: 11123,
}, {
name: 'john',
artist: 'Johanson',
value: 12,
}, ];
}
<pre></pre>
function keysrt(key) {
return function(a,b){
if (a[key] > b[key]) return 1;
if (a[key] < b[key]) return -1;
return 0;
}
}
someArrayOfObjects.sort(keysrt('text'));
Make your life easy and use a closure
https://stackoverflow.com/a/31846142/1001405
You can see the working example here
var filter = 'name', //sort by name
data = [{name:'bob', artist:'rudy'},{name:'johhny', artist:'drusko'},{name:'tiff', artist:'needell'},{name:'top', artist:'gear'}];;
var compare = function (filter) {
return function (a,b) { //closure
var a = a[filter],
b = b[filter];
if (a < b) {
return -1;
}else if (a > b) {
return 1;
} else {
return 0;
}
};
};
filter = compare(filter); //set filter
console.log(data.sort(filter));
Looking at all the answers, I came up with my own solution that works cross-browser. The accepted solution does not work in IE or Safari. Also, the other solutions do not allow for sorting by descending.
/*! FUNCTION: ARRAY.KEYSORT(); **/
Array.prototype.keySort = function(key, desc){
this.sort(function(a, b) {
var result = desc ? (a[key] < b[key]) : (a[key] > b[key]);
return result ? 1 : -1;
});
return this;
}
var arr = [{name:'bob', artist:'rudy'}, {name:'johhny', artist:'drusko'}, {name:'tiff', artist:'needell'}, {name:'top', artist:'gear'}];
arr.keySort('artist');
arr.keySort('artist', true);

Sort JSON with JavaScript Alphabetical before Numerical

So I have some sort code that I wrote in JS, but it sorts numerical before alphabetical and I want it to do alphabetical before numerical. Here is a JSfiddle of it in action.
var sort_by = function(field, reverse, primer){
var key = function(x) {return primer ? primer(x[field]) : x[field]};
return function(a,b) {
var A = key(a), B = key(b);
return ((A < B) ? -1 : (A > B) ? +1 : 0) * [-1,1][+!!reverse];
}
}
var sort_by = function(field, reverse, primer){
var key = function(x) {return primer ? primer(x[field]) : x[field]};
var isNotNumber = function (x) { try {return isNaN(x.substr(0,1)); }catch(e){return false ;}}
var sorter = function(a,b) {
var A = key(a), B = key(b);
if ( !isNotNumber(A) && isNotNumber(B)) return -1;
if ( isNotNumber(A) && !isNotNumber(B)) return +1;
return ((A < B) ? -1 : (A > B) ? +1 : 0) * [-1,1][+!!reverse];
}
return sorter;
}

Categories

Resources