Related
I have a variable and array of objects e.g:
var selectedName = 'fff';
[{
percentage: Math.round(percentage),
count: count,
name: 'bbb',
index: 11,
},
{
percentage: Math.round(percentage),
count: 200,
name: 'aaa',
index: 2,
},
{
percentage: Math.round(percentage),
count: 400,
name: 'All',
index: 7,
},
{
percentage: Math.round(percentage),
count: count,
name: 'fff',
index: 8,
},
{
percentage: Math.round(percentage),
count: count,
name: 'ccc',
index: 3,
}],
I want to sort these as follows: the object which has the name 'All' should always be first. The next object should be the one with the name that matches selectedName, in this case 'fff'. And then the rest of the objects should be ordered by ascending order of their 'index' property.
Is this possible in one Array.sort() method?
Yes, here an example:
var selectedName = 'fff';
let percentage = 1;
let count = 2;
var data=[{percentage:Math.round(percentage),count:count,name:"bbb",index:11},{percentage:Math.round(percentage),count:200,name:"aaa",index:2},{percentage:Math.round(percentage),count:400,name:"All",index:7},{percentage:Math.round(percentage),count:count,name:"fff",index:8},{percentage:Math.round(percentage),count:count,name:"ccc",index:3}];
arr.sort((i, j) => {
if (i.name === j.name && (i.name === 'All' || i.name === selectedName)) return 0;
if (i.name === 'All') return -1;
if (j.name === 'All') return 1;
if (i.name === selectedName) return -1;
if (j.name === selectedName) return 1;
if (i.index < j.index) return -1;
if (i.index > j.index) return 1;
return 0;
})
console.log(arr)
Sure you can do it like so:
var selectedName = 'fff';
var percentage = 23;
var count = 3;
var data=[{percentage:Math.round(percentage),count:count,name:"bbb",index:11},{percentage:Math.round(percentage),count:200,name:"aaa",index:2},{percentage:Math.round(percentage),count:400,name:"All",index:7},{percentage:Math.round(percentage),count:count,name:"fff",index:8},{percentage:Math.round(percentage),count:count,name:"ccc",index:3}];
function sortItems(a, b) {
if (a.name === 'All') {
return -1
}
if (b.name === 'All') {
return 1;
}
if (a.name === selectedName) {
return -1
}
if (b.name === selectedName) {
return 1
}
return a.index - b.index;
}
console.log(items.sort(sortItems));
You can try something like this:
Idea
(a.name !== priorityName) will yield a boolean value. When you use -(minus) operator on them, it is converted to numeric value(true: 1, false: 0).
So if both are neither of priority, both will yield 1 and output will be 0, which is falsey in JS.
Failure of previous expression will call current expression and would loop till last expression
var selectedName = 'fff';
var priorityName = "All";
var percentage = 50.4, count= 0;
var data=[{percentage:Math.round(percentage),count:count,name:"bbb",index:11},{percentage:Math.round(percentage),count:200,name:"aaa",index:2},{percentage:Math.round(percentage),count:400,name:"All",index:7},{percentage:Math.round(percentage),count:count,name:"fff",index:8},{percentage:Math.round(percentage),count:count,name:"ccc",index:3}];
data.sort(function(a,b){
return (a.name !== priorityName) - (b.name !== priorityName) ||
(a.name !== selectedName) - (b.name !== selectedName) ||
a.index - b.index;
})
console.log(data)
You can use a helping object holding the priority of each element. In your particular case, the highest priority has All value.
So firstly we sort the values with higher priority (All, 'fff') and when it's done, we sort the rest by the index value.
Note: Since we are sorting it by ascending order the priority values have to be negative. If we would sort it by a descending order (b - a), they would be positive.
var arr = [{percentage:Math.round("percentage"),count:"count",name:"bbb",index:11},{percentage:Math.round("percentage"),count:200,name:"aaa",index:2},{percentage:Math.round("percentage"),count:400,name:"All",index:7},{percentage:Math.round("percentage"),count:"count",name:"fff",index:8},{percentage:Math.round("percentage"),count:"count",name:"ccc",index:3}],
selectedName = 'fff',
result = arr.sort(function(a,b){
var order = {All: -2, [selectedName]: -1, default: 0};
return (order[a.name] || order.default) - (order[b.name] || order.default) || a.index - b.index;
});
console.log(result);
I have the following code:
var compare = function( nodeA, nodeB ){
return +nodeA.index - +nodeB.index;
};
var sort = function( nodes ){
nodes.sort( compare );
};
The node has this (pseudo) structure:
{
index: <integer>
value: <literal>
}
And it currently sorts them the regular way, when I call the sort function, and print out the index's of each node:
0
1
2
3
How can I change my current logic to make it look like this? :
1
2
3
0 <-- 0 should be considered the biggest index
You can add special handling for zeroes:
var compare = function(nodeA, nodeB) {
// in case both sides are equal
if (nodeA.index === nodeB.index) {
return 0;
}
if (nodeA.index === 0) {
return 1;
}
if (nodeB.index === 0) {
return -1;
}
return +nodeA.index - +nodeB.index;
};
var data = [{
index: 2,
value: 'a'
}, {
index: 0,
value: 'b'
}, {
index: 3,
value: 'c'
}, {
index: 1,
value: 'd'
}]
data.sort(compare);
console.log(data);
You can first sort by condition that index != 0 and then just sort by index value.
var data = [{
index: 2,
value: 'a'
}, {
index: 0,
value: 'b'
},{
index: 3,
value: 'c'
},{
index: 1,
value: 'd'
}]
var result = data.sort(function(a, b) {
return (b.index != 0) - (a.index != 0) || (a.index - b.index)
})
console.log(result)
You just need to change your compare function a little bit:
var compare = function( nodeA, nodeB ){
if (!+nodeA.index) return 1;
if (!+nodeB.index) return -1;
return +nodeA.index - +nodeB.index;
};
Just change compare to:
var compare = function( nodeA, nodeB ){
return ((+nodeA.index || Infinity) - (+nodeB.index || Infinity)) || Infinity;
};
|| operator returns the first value that is not "falsy", which is a "truthy" value, but is also the actual value. This is a EMCA5 "trick" to create default values to variables.
So explaining:
for nodeA.index == 0 && nodeB.index > 0 => Infinity - somevalue == Infinity
for nodeA.index > 0 && nodeB.index == 0 => somevalue - Infinity == -Infinity
for nodeA.index == 0 && nodeB.index == 0 => Infinity - Infinity == NaN in which case the || Infinity option is choosen
Alright, so I have an array of objects that includes null values for a certain property.
The object looks roughly like this for sorting purposes... (40 elements, but this will suffice...).
It needs to be sorted based on roulette descending (with roulette sometimes being null), then novelty, then popularity.
My head is getting a bit crushed.
This works to sort the roulette in descending, but how do I need to extend it to include the other two criteria?
Object:
[
{
title: 'one',
popularity: 4,
novelty: 3
},
{
title: 'two',
popularity: 1
novelty: 4
},
{
title: 'three',
popularity: 5,
novelty: 3,
roulette: 0
},
{
title: 'four',
popularity: 5,
novelty: 3,
roulette: 1
}
]
Partially working function:
object.sort(function(a, b) {
if (a['roulette'] == null) return 1
if (b['roulette'] == null) return -1
if (a['roulette'] === b['roulette']) return 0
return b.roulette > a.roulette ? 1 : -1
});
An attempt with sorting with priority and groups.
var data = [{ title: 'one', popularity: 4, novelty: 3 }, { title: 'two', popularity: 1, novelty: 4 }, { title: 'three', popularity: 5, novelty: 3, roulette: 0 }, { title: 'four', popularity: 5, novelty: 3, roulette: 1 }, { title: 'five', popularity: 5, novelty: 4, roulette: null }, { title: 'six', popularity: 5, novelty: 5, roulette: undefined }];
data.sort(function (a, b) {
return (
(a.roulette === undefined || a.roulette === null) - (b.roulette === undefined || b.roulette === null) ||
a.roulette - b.roulette ||
a.novelty - b.novelty ||
a.popularity - b.popularity
);
});
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can try sorting based on weighted rank:
var data=[{title:"one",popularity:4,novelty:3},{title:"two",popularity:1,novelty:4},{title:"three",popularity:5,novelty:3,roulette:0},{title:"four",popularity:5,novelty:3,roulette:1}];
data.sort(function(a, b) {
var r1 = a.roulette === undefined ? -1 : a.roulette;
var r2 = b.roulette === undefined ? -1 : b.roulette;
var n1 = a.novelty === undefined ? -1 : a.novelty;
var n2 = b.novelty === undefined ? -1 : b.novelty;
var p1 = a.popularity === undefined ? -1 : a.popularity;
var p2 = b.popularity === undefined ? -1 : b.popularity;
var r_rank = r1 > r2 ? -100 : r1 < r2 ? 100 : 0;
var n_rank = n1 > n2 ? -10 : n1 < n2 ? 10 : 0;
var p_rank = p1 > p2 ? -1 : p1 < p2 ? 1 : 0;
return r_rank + n_rank + p_rank;
})
var r_rank = r1 > r2 ? -100 : r1 < r2 ? 100 : 0;
var n_rank = n1 > n2 ? -10 : n1 < n2 ? 10 : 0;
var p_rank = p1 > p2 ? -1 : p1 < p2 ? 1 : 0;
return r_rank + n_rank + p_rank;
})
console.log(data)
Just include more conditionals:
var data = [{"title":"one","popularity":4,"novelty":3},{"title":"two","popularity":1,"novelty":4},{"title":"three","popularity":5,"novelty":3,"roulette":0},{"title":"four","popularity":5,"novelty":3,"roulette":1}];
data.sort(function(a,b) {
if (a.roulette < b.roulette || a.roulette == null) return +1;
if (a.roulette > b.roulette || b.roulette == null) return -1;
if (a.novelty < b.novelty || a.novelty == null) return +1;
if (a.novelty > b.novelty || b.novelty == null) return -1;
if (a.popularity < b.popularity || a.popularity == null) return +1;
if (a.popularity > b.popularity || b.popularity == null) return -1;
return 0;
})
console.log(data);
You just have to keep on breaking ties if one parameter is same.
obj.sort(function(a, b) {
var rouletteDiff = compare(a.roulette, b.roulette);
if(rouletteDiff != 0) return rouletteDiff;
var noveltyDiff = compare(a.novelty, b.novelty);
if(noveltyDiff != 0) return noveltyDiff;
return compare(a.popularity, b.popularity);
});
function compare(x,y){
if(x == undefined) return 1;
if(y == undefined) return -1;
if(x === y){
return 0;
}else{
return x > y ? -1 : 1
}
}
Here is the preferential sorting (descending) with roulette, novelty and popularity (in that order)
This handles both null and undefined - check out the demo below:
var object=[{title:"one",popularity:4,novelty:3},{title:"two",popularity:1,novelty:4},{title:"three",popularity:5,novelty:3,roulette:0},{title:"four",popularity:5,novelty:3,roulette:1},{title:"five",roulette:4,novelty:null},{title:"six",popuplarity:7},{title:"seven",novelty:8,roulette:null},{title:"eight",novelty:0},{title:"nine",popularity:10}];
function ifNumber(num) {
if(num == undefined || num == null)
return -Infinity;
else
return num;
}
var result = object.sort(function(a, b) {
return (ifNumber(b.roulette) - ifNumber(a.roulette))
|| (ifNumber(b.novelty) - ifNumber(a.novelty))
|| (ifNumber(b.popuplarity) - ifNumber(a.popuplarity));
});
console.log(result);
.as-console-wrapper{top:0;max-height:100%!important;}
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; }
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));
});
}