Related
Example:
hash = {'Apple':2, 'Orange' :1 , 'Mango' : 2}
here the maximum key is both Apple and Mango. How do I write a function which gives both Apple and Mango as answer.
I tried something like this :
Object.keys(hash).reduce(function(a, b){ return hash[a] > hash[b] ? a : b });
But this gives only Apple as the answer.
You could first calculate the max value as a separate operation and then just filter:
const hash = {Apple: 2, Orange: 1, Mango: 2};
const max = Object.keys(hash).reduce((a, v) => Math.max(a, hash[v]), -Infinity);
const result = Object.keys(hash).filter(v => hash[v] === max);
console.log(result);
Simple and readable, but it requires and extra iteration, so it's not the most efficient implementation.
Your reduce can only return a single value, but you need an array of values. So create one as the initial value.
In your function body, first, check whether your next key has a larger value, in which case you clear out the array.
Then, if your array is empty, or if the first contained key (and thus all keys) have the same value, push that key into your array.
Also, it helps if you give your arguments more expressive names.
Object.keys(hash).reduce(function(longestKeys, key){
if(hash[key] > hash[longestKeys[0]]){
longestKeys.length = 0;
}
if(longestKeys.length === 0 || hash[key] === hash[longestKeys[0]]){
longestKeys.push(key);
}
return longestKeys;
}, []);
You might transform the object into one whose properties correspond to the count, whose values are the (original) property names in an array, and then get the value of the property with the maximum count:
const hash = {'Apple':2, 'Orange' :1 , 'Mango' : 2};
const indexedByCount = Object.entries(hash).reduce((a, [key, val]) => {
if (!a[val]) a[val] = [];
a[val].push(key);
return a;
}, {});
console.log(
indexedByCount[Math.max(...Object.keys(indexedByCount))]
);
A less functional but more efficient method would be to keep track of a max variable corresponding to the maximum value found so far:
const hash = {'Apple':2, 'Orange' :1 , 'Mango' : 2};
let max = -Infinity;
console.log(
Object.entries(hash).reduce((a, [key, val]) => {
if (val > max) {
max = val;
return [key];
}
if (val === max) a.push(key);
return a;
}, [])
);
This works. If max changes clear the result and set only the max value.
var hash = {'Apple':2, 'Orange':1 , 'Mango':2, "Jackfruit":10, "Pineapple":5, "Tomato":-4};
var max="";
var result = Object.keys(hash).reduce(function(acc, val){
if(max < hash[val]) (max=hash[val], acc={});
if(hash[val]==max) acc[val] = hash[val];
return acc;
},{});
console.log(result)
I believe the requirements are traverse the array only once and results is an array of keys
const hash = {'Apple':2, 'Orange' :1 , 'Mango' : 2};
let max = 0;
let result = [];
Object.getOwnPropertyNames(hash).forEach(k => {
if (hash[k] > max) {
result = [k];
max = hash[k];
} else if (hash[k] === max) {
result.push(k);
}
});
console.log(result);
I want to sort the below array by the "name" that is inside "user" object
var myArr = [
{"id":1,"user":{"name":"allen","id":101}},
{"id":2,"user":{"name":"martin","id":102}}
]
how can I do this?
I have a method to sort array of objects but I can't use it for array of objects of objects
this is the method:
function dynamicSort(property) {
var sortOrder = 1;
if (property[0] === "-") {
sortOrder = -1;
property = property.substr(1);
}
return function (a, b) {
var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
return result * sortOrder;
}
}
then I can sort using this:
myArr.sort(dynamicSort("id"));
I would create property as a getter function (For the complex examples. You could check if propFn is a function, use this below for the more complex ones. See this answer for checking is propFn is a function.):
var myArr = [
{"id":1,"user":{"name":"allen","id":101}},
{"id":2,"user":{"name":"martin","id":102}}
]
function dynamicSort(propFn, sortOrder = 1) {
return function (a, b) {
var result = (propFn(a) < propFn(b)) ? -1 : (propFn(a) > propFn(b)) ? 1 : 0;
return result * sortOrder;
}
}
console.log(myArr.sort(dynamicSort((obj) => obj.user.name)));
console.log(myArr.sort(dynamicSort((obj) => obj.user.name, -1)));
Alternatively, you can take a look at: Convert JavaScript string in dot notation into an object reference
This will give you an idea of how you can convert period notation into a nested object, but I recommend reading the disclaimer at the top.
To maintain backwards compatibility, you could use something like this below:
var myArr = [
{"id":1,"user":{"name":"allen","id":101}},
{"id":2,"user":{"name":"martin","id":102}}
]
function dynamicSort(propFn, sortOrder = 1) {
if (typeof propFn === "string") {
let prop = propFn;
if (prop[0] === "-") {
sortOrder = -1;
prop = prop.substr(1);
}
propFn = (obj) => obj[prop];
}
return function (a, b) {
var result = (propFn(a) < propFn(b)) ? -1 : (propFn(a) > propFn(b)) ? 1 : 0;
return result * sortOrder;
}
}
console.log(myArr.sort(dynamicSort((obj) => obj.user.name)));
console.log(myArr.sort(dynamicSort((obj) => obj.user.name, -1)));
console.log(myArr.sort(dynamicSort("id")));
console.log(myArr.sort(dynamicSort("-id")));
Edit:
If you are experiencing problems because of periods in your key names, this approach may be better suited as a solution. The path just has to either start as a bracket notation accessor or with a dot:
function dynamicSort(property, order) {
order||(order=1);
const getter = new Function("obj", "return obj" + property + ";");
return function(a, b) {
var result = (getter(a) < getter(b)) ? -1 : (getter(a) > getter(b)) ? 1 : 0;
return result * order;
}
}
var myArr = [{
"id": 1,
"user": {
"name": "allen",
"id": 101
}
},
{
"id": 2,
"user": {
"name": "martin",
"id": 102
}
},
{
"id": 3,
"user": {
"name": "barry",
"id": 103
}
}
]
console.log(JSON.stringify(myArr.sort(dynamicSort(".user.name"))));
Using the Object.byString() method from this answer, you can rewrite your function to take a path to the property you want to sort by:
var myArr = [
{"id":1,"user":{"name":"allen","id":101}},
{"id":2,"user":{"name":"martin","id":102}},
{"id":3,"user":{"name":"barry","id":103}}
]
console.log(JSON.stringify(myArr.sort(dynamicSort("user.name"))));
function dynamicSort(property) {
var sortOrder = 1;
if (property[0] === "-") {
sortOrder = -1;
property = property.substr(1);
}
return function (a, b) {
var result = (byString(a, property) < byString(b, property)) ? -1 : (byString(a, property) > byString(b, property)) ? 1 : 0;
return result * sortOrder;
}
}
function byString(o, s) {
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
s = s.replace(/^\./, ''); // strip a leading dot
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
}
I think it would be a little clearer and easier to use if you have the order as a second parameter, which means that your function should then more or less look like this:
function dynamicSort(property, order) {
return function(a, b) {
var result = (byString(a, property) < byString(b, property)) ? -1 : (byString(a, property) > byString(b, property)) ? 1 : 0;
return result * order;
}
}
You could use sort method but you first need to get nested property and for that you could pass a string and then use reduce method to get property.
var myArr = [{"id":2,"user":{"name":"martin","id":102}}, {"id":1,"user":{"name":"allen","id":101}}]
function dynamicSort(arr, prop) {
function getVal(obj, prop) {
return prop.split('.').reduce((r, e) => r[e] || {}, obj)
}
arr.sort((a, b) => {
let vA = getVal(a, prop);
let vB = getVal(b, prop);
return vA.localeCompare(vB)
})
}
dynamicSort(myArr, "user.name")
console.log(myArr)
Check out this SO answer for an answer to a fundamentally similar question.
Since the function in that answer is called differently than yours, with the array to be sorted passed in as a parameter, you could refactor it to be called in the same way as your existing dynamicSort function as follows:
function dynamicSort(property) {
var sortOrder = 1;
if (property[0] === "-") {
sortOrder = -1;
property = property.substr(1);
}
var prop = property.split('.');
var len = prop.length;
return function (a, b) {
var i = 0;
while( i < len ) { a = a[prop[i]]; b = b[prop[i]]; i++; }
var result = (a < b) ? -1 : (a > b) ? 1 : 0;
return result * sortOrder;
}
}
You could then call it like so: myArr.sort(this.dynamicSort("user.name")).
Here is a working snippet to demonstrate:
function dynamicSort(property) {
var sortOrder = 1;
if (property[0] === "-") {
sortOrder = -1;
property = property.substr(1);
}
var prop = property.split('.');
var len = prop.length;
return function (a, b) {
var i = 0;
while( i < len ) { a = a[prop[i]]; b = b[prop[i]]; i++; }
var result = (a < b) ? -1 : (a > b) ? 1 : 0;
return result * sortOrder;
}
}
var myArr = [
{"id":1,"user":{"name":"allen","id":101}},
{"id":2,"user":{"name":"martin","id":102}},
{"id":3,"user":{"name":"beth","id":103}},
];
console.log(myArr.sort(this.dynamicSort("user.name"))); //expected output: [{id:1, user:{name:"allen",...}}, {id:3, user:{name:"beth",...}}, {id:2, user:{name:"martin",...}}]
Try this out. Works fine for me
var sortedArray = myArr.sort((a, b) => {
const
nameA = a.user.name.toUpperCase(),
nameB = b.user.name.toUpperCase();
if(nameA < nameB)
return -1;
if(nameA > nameB)
return 1;
return 0;
});
myArr.sort(function(a, b) {
return a.user.name.localeCompare(b.user.name);
});
Why doesn't a.push(b) work in my Array.reduce()? a=a.push(b) where b is a string, turns a to an integer.?!
getHighestValuesInFrequency: function(frequency) {
//Input:var frequency = {mats: 1,john: 3,johan: 2,jacob: 3};
//Output should become ['John','jacob']
var objKeys = Object.keys(frequency);
var highestVal = objKeys.reduce((a,b)=>
{highestVal = (frequency[b] > a)? frequency[b] : a;
return highestVal;},0);
var winner = objKeys.reduce((a,b)=>
{ a = (frequency[b] === highestVal)? a.push(b) : a;
return a},[]);
return winner;
}
Since push() returns the new length of the array, you're assigning the length to a. Instead of a conditional operator, use an if statement.
var winner = objKeys.reduce((a, b) => {
if (frequency[b] === highestVal) {
a.push(b);
}
return a;
}, []);
The push() returns the new length. You can use ES2015 spread syntax:
var winner = objKeys.reduce((a, b)=> {
a = (frequency[b] === highestVal)? [...a, b] : a;
return a
}, []);
Building on the excellent answer by #Alexander Moiseyev.
You can avoid setting and mutating both variables a and winner by doing the following:
return objKeys.reduce((acc, val) =>
frequency[val] === highestVal
? [...acc, val]
: acc
,[])
note: for clarity I have explicitly declared the accumulator and value in this reduce method.
Please note that this structure you provide is not clear enough
I would use instead an array of objects each having a name and a frecuency
var frequencies = [{name : "mats", frecuency : 1},
{name : "john", frecuency: 3},
{name : "johan", frecuency: 2},
{name : "jacob", frecuency: 3}];
Then you can use a filter operation and map to get what you need
var max = Math.max.apply(Math, frequencies.map(function(o){return o.frecuency;}));
var maxElems = frequencies.filter(function(a){return a.frecuency == max}).map(function(a){return a.name;});
maxElems will give you the names of the people with higher frecuency
The spread syntax enables an even shorter way:
const winner = objKeys.reduce((a, b) => {
return frequency[b] === highestVal ? [...a, b] : a
}, [])
I have an array of objects like this:
var booksArray = [
{Id:1,Rate:5,Price:200,Name:"History"},
{Id:2,Rate:5,Price:150,Name:"Geographic"},
{Id:3,Rate:1,Price:75,Name:"Maths"},
{Id:4,Rate:2,Price:50,Name:"Statistics"},
{Id:5,Rate:3,Price:120,Name:"Drawing"},
{Id:6,Rate:2,Price:100,Name:"Arts"},
{Id:7,Rate:3,Price:110,Name:"Chemistry"},
{Id:8,Rate:4,Price:160,Name:"Biology"},
{Id:9,Rate:4,Price:90,Name:"Software Engineering"},
{Id:10,Rate:4,Price:80,Name:"Algorithm"},
{Id:11,Rate:1,Price:85,Name:"Urdu"},
{Id:12,Rate:1,Price:110,Name:"International Relations"},
{Id:13,Rate:1,Price:120,Name:"Physics"},
{Id:14,Rate:3,Price:230,Name:"Electronics"},
{Id:15,Rate:3,Price:250,Name:"Jihad"},
{Id:16,Rate:3,Price:200,Name:"Functional Programming"},
{Id:17,Rate:2,Price:190,Name:"Computer Science"},
{Id:18,Rate:2,Price:50,Name:"Problem solving Techniques"},
{Id:19,Rate:6,Price:150,Name:"C#"},
{Id:20,Rate:7,Price:250,Name:"ASP.Net"}
]
I am sorting it with this function:
function sortBy(key, reverse) {
var moveSmaller = reverse ? 1 : -1;
var moveLarger = reverse ? -1 : 1;
return function(a, b) {
if (a[key] < b[key]) {
return moveSmaller;
}
if (a[key] > b[key]) {
return moveLarger;
}
return 0;
};
}
booksArray.sort(sortBy('Rate', false))
console.log(JSON.stringify(booksArray))
And this is producing this result:
[
{Id:3,Rate:1,Price:75,Name:"Maths"},
{Id:11,Rate:1,Price:85,Name:"Urdu"},
{Id:12,Rate:1,Price:110,Name:"International Relations"},
{Id:13,Rate:1,Price:120,Name:"Physics"},
{Id:4,Rate:2,Price:50,Name:"Statistics"},
{Id:6,Rate:2,Price:100,Name:"Arts"},
{Id:17,Rate:2,Price:190,Name:"Computer Science"},
{Id:18,Rate:2,Price:50,Name:"Problem solving Techniques"},
{Id:5,Rate:3,Price:120,Name:"Drawing"},
{Id:7,Rate:3,Price:110,Name:"Chemistry"},
{Id:14,Rate:3,Price:230,Name:"Electronics"},
{Id:15,Rate:3,Price:250,Name:"Jihad"},
{Id:16,Rate:3,Price:200,Name:"Functional Programming"},
{Id:8,Rate:4,Price:160,Name:"Biology"},
{Id:9,Rate:4,Price:90,Name:"Software Engineering"},
{Id:10,Rate:4,Price:80,Name:"Algorithm"},
{Id:1,Rate:5,Price:200,Name:"History"},
{Id:2,Rate:5,Price:150,Name:"Geographic"},
{Id:19,Rate:6,Price:150,Name:"C#"},
{Id:20,Rate:7,Price:250,Name:"ASP.Net"}
]
You can see it is sorting on the Rate field which is fine. Now I want to resort again the parts of array without disturbing Rate sorting. I need output like this:
[
{Id:12,Rate:1,Price:110,Name:"International Relations"},
{Id:3,Rate:1,Price:75,Name:"Maths"},
{Id:13,Rate:1,Price:120,Name:"Physics"},
{Id:11,Rate:1,Price:85,Name:"Urdu"},
{Id:6,Rate:2,Price:100,Name:"Arts"},
{Id:17,Rate:2,Price:190,Name:"Computer Science"},
{Id:18,Rate:2,Price:50,Name:"Problem solving Techniques"},
{Id:4,Rate:2,Price:50,Name:"Statistics"},
{Id:7,Rate:3,Price:110,Name:"Chemistry"},
{Id:5,Rate:3,Price:120,Name:"Drawing"},
{Id:14,Rate:3,Price:230,Name:"Electronics"},
{Id:16,Rate:3,Price:200,Name:"Functional Programming"},
{Id:15,Rate:3,Price:250,Name:"Jihad"},
{Id:10,Rate:4,Price:80,Name:"Algorithm"},
{Id:8,Rate:4,Price:160,Name:"Biology"},
{Id:9,Rate:4,Price:90,Name:"Software Engineering"},
{Id:2,Rate:5,Price:150,Name:"Geographic"},
{Id:1,Rate:5,Price:200,Name:"History"},
{Id:19,Rate:6,Price:150,Name:"C#"},
{Id:20,Rate:7,Price:250,Name:"ASP.Net"}
]
Here you can see it is sorted on Rate as well as Name.
Why am I not doing sorting with multiple keys once and for all? Because I am creating a function which sorts the array of objects and can be called multiple times.
Example:
myarray.order('Rate') // single sort
myarray.order('Rate').order('Name') // multiple sort
I can use some more parameters in my function to track if array has already been sorted.
Sample Fiddle
Assuming that you already have bookArray sorted by Rate. It can be sorted on second level using following. Can be fine tuned better for easy usability.
var arr = bookArray;
var firstSortKey = 'Rate';
var secondSortKey = 'Name';
var count = 0 ;
var firstSortValue = arr[count][firstSortKey];
for(var i=count+1; i<arr.length; i++){
if(arr[i][firstSortKey]!==firstSortValue){
var data = arr.slice(count, i);
data = data.sort(function(a, b){
return a[secondSortKey]>b[secondSortKey];
});
var argsArray = [count, i-count];
argsArray.push.apply(argsArray, data);
Array.prototype.splice.apply(arr, argsArray);
count = i;
firstSortValue = arr[i][firstSortKey];
}
}
Fiddle Demo
Change your sortBy function to accept an array of keys. Something like:
function sortBy(keys, reverse) {
var moveSmaller = reverse ? 1 : -1;
var moveLarger = reverse ? -1 : 1;
var key = keys.shift();
return function(a, b) {
if (a[key] < b[key]) {
return moveSmaller;
}
if (a[key] > b[key]) {
return moveLarger;
}
return keys.length ? (sortBy(keys, reverse))(a, b) : 0;
};
}
booksArray.sort(sortBy(['Rate', 'Name'], false))
(Untested, jsfiddle is down for me)
Just for fun: here's a generic sorter for multiple levels of field values within an Array of Objects.
Syntax
XSort().create([arrOfObjects])
.orderBy(
{key: [keyname], [descending=0/1] },
{key: [keyname], [descending=0/1]},
... );
See jsFiddle for an example using booksArray.
function XSort() {
const multiSorter = sortKeys => {
if (!sortKeys || sortKeys[0].constructor !== Object) {
throw new TypeError("Provide at least one {key: [keyname]} to sort on");
}
return function (val0, val1) {
for (let sortKey of sortKeys) {
const v0 = sortKey.key instanceof Function
? sortKey.key(val0) : val0[sortKey.key];
const v1 = sortKey.key instanceof Function
? sortKey.key(val1) : val1[sortKey.key];
const isString = v0.constructor === String || v1.constructor === String;
const compare = sortKey.descending ?
isString ? v1.toLowerCase().localeCompare(v0.toLowerCase()) : v1 - v0 :
isString ? v0.toLowerCase().localeCompare(v1.toLowerCase()) : v0 - v1;
if ( compare !== 0 ) { return compare; }
}
};
}
const Sorter = function (array) {
this.array = array;
};
Sorter.prototype = {
orderBy: function(...sortOns) {
return this.array.slice().sort(multiSorter(sortOns));
},
};
return {
create: array => new Sorter(array)
};
}
I have this function to sort a JavaScript array of objects based on a property:
// arr is the array of objects, prop is the property to sort by
var sort = function (prop, arr) {
arr.sort(function (a, b) {
if (a[prop] < b[prop]) {
return -1;
} else if (a[prop] > b[prop]) {
return 1;
} else {
return 0;
}
});
};
It works with arrays like this:
sort('property', [
{property:'1'},
{property:'3'},
{property:'2'},
{property:'4'},
]);
But I want to be able to sort also by nested properties, for example something like:
sort('nestedobj.property', [
{nestedobj:{property:'1'}},
{nestedobj:{property:'3'}},
{nestedobj:{property:'2'}},
{nestedobj:{property:'4'}}
]);
However this doesn't work because it is not possible to do something like object['nestedobj.property'], it should be object['nestedobj']['property'].
Do you know how could I solve this problem and make my function work with properties of nested objects?
Thanks in advance
You can split the prop on ., and iterate over the Array updating the a and b with the next nested property during each iteration.
Example: http://jsfiddle.net/x8KD6/1/
var sort = function (prop, arr) {
prop = prop.split('.');
var len = prop.length;
arr.sort(function (a, b) {
var i = 0;
while( i < len ) { a = a[prop[i]]; b = b[prop[i]]; i++; }
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
});
return arr;
};
Use Array.prototype.sort() with a custom compare function to do the descending sort first:
champions.sort(function(a, b) { return b.level - a.level }).slice(...
Even nicer with ES6:
champions.sort((a, b) => b.level - a.level).slice(...
Instead of passing the property as a string, pass a function that can retrieve the property from the top level object.
var sort = function (propertyRetriever, arr) {
arr.sort(function (a, b) {
var valueA = propertyRetriever(a);
var valueB = propertyRetriever(b);
if (valueA < valueB) {
return -1;
} else if (valueA > valueB) {
return 1;
} else {
return 0;
}
});
};
Invoke as,
var simplePropertyRetriever = function(obj) {
return obj.property;
};
sort(simplePropertyRetriever, { .. });
Or using a nested object,
var nestedPropertyRetriever = function(obj) {
return obj.nestedObj.property;
};
sort(nestedPropertyRetriever, { .. });
if you have array of objects like
const objs = [{
first_nom: 'Lazslo',
last_nom: 'Jamf',
moreDetails: {
age: 20
}
}, {
first_nom: 'Pig',
last_nom: 'Bodine',
moreDetails: {
age: 21
}
}, {
first_nom: 'Pirate',
last_nom: 'Prentice',
moreDetails: {
age: 22
}
}];
you can use simply
nestedSort = (prop1, prop2 = null, direction = 'asc') => (e1, e2) => {
const a = prop2 ? e1[prop1][prop2] : e1[prop1],
b = prop2 ? e2[prop1][prop2] : e2[prop1],
sortOrder = direction === "asc" ? 1 : -1
return (a < b) ? -sortOrder : (a > b) ? sortOrder : 0;
}
and call it
for direct objects
objs.sort(nestedSort("last_nom"));
objs.sort(nestedSort("last_nom", null, "desc"));
for nested objects
objs.sort(nestedSort("moreDetails", "age"));
objs.sort(nestedSort("moreDetails", "age", "desc"));
You can use Agile.js for this kind of things.
Actually you pass an expression instead of callback, it's handle nested properties and javascript expression in a very nice-ish way.
Usage: _.orderBy(array, expression/callback, reverse[optional])
Example:
var orders = [
{ product: { price: 91.12, id: 1 }, date: new Date('01/01/2014') },
{ product: { price: 79.21, id: 2 }, date: new Date('01/01/2014') },
{ product: { price: 99.90, id: 3 }, date: new Date('01/01/2013') },
{ product: { price: 19.99, id: 4 }, date: new Date('01/01/1970') }
];
_.orderBy(orders, 'product.price');
// → [orders[3], orders[1], orders[0], orders[2]]
_.orderBy(orders, '-product.price');
// → [orders[2], orders[0], orders[1], orders[3]]
Would this meet your needs?
// arr is the array of objects, prop is the property to sort by
var sort = function (nestedObj, prop, arr) {
arr.sort(function (a, b) {
if (a[nestedObj][prop] < b[nestedObj][prop]) {
return -1;
} else if (a[nestedObj][prop] > b[nestedObj][prop]) {
return 1;
} else {
return 0;
}
});
};
Try this (used a recursive function to get nested value, you can pass the nested property as nestedobj.property):
You can use this for any level of hierarchy
// arr is the array of objects, prop is the property to sort by
var getProperty = function(obj, propNested){
if(!obj || !propNested){
return null;
}
else if(propNested.length == 1) {
var key = propNested[0];
return obj[key];
}
else {
var newObj = propNested.shift();
return getProperty(obj[newObj], propNested);
}
};
var sort = function (prop, arr) {
arr.sort(function (a, b) {
var aProp = getProperty(a, prop.split("."));
var bProp = getProperty(a, prop.split("."));
if (aProp < bProp) {
return -1;
} else if (aProp > bProp) {
return 1;
} else {
return 0;
}
});
};
This is my modify code.
// arr is the array of objects, prop is the property to sort by
var s = function (prop, arr) {
// add sub function for get value from obj (1/2)
var _getVal = function(o, key){
var v = o;
var k = key.split(".");
for(var i in k){
v = v[k[i]];
}
return v;
}
return arr.sort(function (a, b) {
// get value from obj a, b before sort (2/2)
var aVal = _getVal(a, prop);
var bVal = _getVal(b, prop);
if (aVal < bVal) {
return -1;
} else if (aVal > bVal) {
return 1;
} else {
return 0;
}
});
};
var objectsArr = [
{nestedobj:{property:'1'}},
{nestedobj:{property:'3'}},
{nestedobj:{property:'2'}},
{nestedobj:{property:'4'}}
];
function getFromPath(obj, path) {
let r = obj;
path.forEach(key => { r = r[key]})
return r
}
function sortObjectsArr(objectsArray, ...path) {
objectsArray.sort((a, b) => getFromPath(a, path) - getFromPath(b, path))
}
sortObjectsArr(objectsArr, 'nestedobj', 'property');
console.log(objectsArr);
Unfortunately, I didn't find any nice way to use the arguments in order to access the attributes of the nested object.
Want to mention that there can be some checks if the keys are available in the passed object, but this depends on who and how want to implement this.
Description
My solution is this one. I decide to flat the object first:
function flattenObject(value: any): any {
let toReturn: any = {};
for (const i in value) {
if (!value.hasOwnProperty(i)) {
continue;
}
if (typeof value[i] == 'object') {
const flatObject = flattenObject(value[i]);
for (const x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue;
toReturn[i + '.' + x] = flatObject[x];
}
} else {
toReturn[i] = value[i];
}
}
return toReturn;
}
And then I'll extract the value from the object:
function nestedFieldValue(
nestedJoinedFieldByDot: string,
obj: any,
): any {
return flattenObject(obj)[nestedJoinedFieldByDot];
}
Ant at the end I just need to do this:
export function fieldSorter(fields: string[]) {
return function (a: any, b: any) {
return fields
.map(function (fieldKey) {
// README: Sort Ascending by default
let dir = 1;
if (fieldKey[0] === '-') {
// README: Sort Descending if `-` was passed at the beginning of the field name
dir = -1;
fieldKey = fieldKey.substring(1);
}
const aValue = nestedFlattenObjectFieldValue(
fieldKey,
a,
);
const bValue = nestedFlattenObjectFieldValue(
fieldKey,
b,
);
if (
typeof aValue === 'number' ||
typeof bValue === 'number'
) {
/**
* README: default value when the field does not exists to prevent unsorted array
* I assume that 0 should be the last element. In other word I sort arrays in a way
* that biggest numbers comes first and then smallest numbers
*/
if (aValue ?? 0 > bValue ?? 0) {
return dir;
}
if (aValue ?? 0 < bValue ?? 0) {
return -dir;
}
} else {
if (aValue ?? 0 > bValue ?? 0) {
return dir;
}
if (aValue ?? 0 < bValue ?? 0) {
return -dir;
}
}
return 0;
})
.reduce(function firstNonZeroValue(p, n) {
return p ? p : n;
}, 0);
};
}
Finally we need to do this:
const unsorted = [
{
city: {
priority: 1,
name: 'Tokyo',
airport: { name: 'Haneda Airport' }
}
}
]
const result = unsorted.sort(
fieldSorter(['city.priority', 'city.airport.name', 'city.name']),
);
I think this way is much much clear and cleaner. It is readable and more functional. I merge multiple answer from stackoverflow to reach this solution :sweat_smile:
This should work and can accept multiple parameters.
https://codepen.io/allenACR/pen/VwQKWZG
function sortArrayOfObjects(items, getter) {
const copy = JSON.parse(JSON.stringify(items));
const sortFn = fn => {
copy.sort((a, b) => {
a = fn(a)
b = fn(b)
return a === b ? 0 : a < b ? -1 : 1;
});
};
getter.forEach(x => {
const fn = typeof x === 'function' ? x : item => item[x];
sortFn(fn);
});
return copy;
}
// example dataset
const data = [
{id: 3, name: "Dave", details: {skill: "leader"} },
{id: 1, name: "Razor", details: {skill: "music"} },
{id: 2, name: "Syd", details: {skill: "animal husbandry"} }
]
// sort via single prop
const sort1 = sortArrayOfObjects(data, ["id"])
// returns [Razor, Syd, Dave]
// sort via nested
const sort2 = sortArrayOfObjects(data, [
(item) => item.details.skill
])
// returns [Syd, Dave, Razor]
console.log({sort1, sort2})
3 levels deep. path can look like this.
'level1' or 'level1.level2' or 'level1.level2.level3'
I also did uppercase for the sort all my items are strings.
Anwser is a modified version from - #Mas
public keysrt(arr: Object[], path: string, reverse: boolean): void {
const nextOrder = reverse ? -1 : 1;
const pathSplit = path.split('.');
if (arr === null || arr === undefined ) {
return;
}
if (arr.length <= 1) {
return;
}
const nestedSort = (prop1, prop2 = null, prop3 = null, direction = 'asc') => (e1, e2) => {
const a = prop3 ? e1[prop1][prop2][prop3] : prop2 ? e1[prop1][prop2] : e1[prop1],
b = prop3 ? e2[prop1][prop2][prop3] : prop2 ? e2[prop1][prop2] : e2[prop1],
sortOrder = direction === 'asc' ? 1 : -1;
return (a.toString().toUpperCase() < b.toString().toUpperCase()) ?
-sortOrder * nextOrder : (a.toString().toUpperCase() > b.toString().toUpperCase()) ?
sortOrder * nextOrder : 0;
};
if (pathSplit.length === 3) {
arr.sort(nestedSort(pathSplit[0], pathSplit[1], pathSplit[2]));
}
if (pathSplit.length === 2) {
arr.sort(nestedSort(pathSplit[0], pathSplit[1]));
}
if (pathSplit.length === 1) {
arr.sort(nestedSort(pathSplit[0], null));
}
}
For those who a researching on how to sort nested properties. I created a small type-safe array sorting method with support for deeply nested properties and Typescript autocompletion.
https://github.com/jvandenaardweg/sort-by-property
https://www.npmjs.com/package/sort-by-property
Example:
blogPosts.sort(sortByProperty('author.name', 'asc'));