I have the following response I get from my server:
[
{"key":{"name":"1","kind":"a"},
{"key":{"name":"1","kind":"ap"},
{"key":{"name":"5","kind":"ap"}
]
The responses come in a different order every time as the server does not guarantee order. Thus I need to sort the response like this after I receive it:
First sort so the smallest KIND is first so 'a' should come before 'b'. I then need to make it so name of the username is the first ordered within the 'a'.
var username = '5';
var response = [
{"key":{"name":"1","kind":"a"},
{"key":{"name":"1","kind":"ap"},
{"key":{"name":"5","kind":"ap"}
];
response = entities.sort(function (a, b) {
if (a.key.kind != b.key.kind){ return a.key.kind < b.key.kind}
else if(a.key.name == username){ return a.key.name < b.key.name }
else return a.key.name > b.key.name;
});
This is the code I use to sort, but it does not work. It sorts the KIND correctly, but then when it needs to sort by NAME (username should come before other names) it does not work.
The actual result I get is equal to this:
[
{"key":{"name":"1","kind":"ap"},
{"key":{"name":"5","kind":"ap"},
{"key":{"name":"1","kind":"a"}
]
But the result I want is this:
[
{"key":{"name":"5","kind":"ap"},
{"key":{"name":"1","kind":"ap"},
{"key":{"name":"1","kind":"a"}
]
As you can see my username is equal to 5, so {"key":{"name":"5","kind":"ap"} should come before {"key":{"name":"1","kind":"ap"} .
return a.key.name < b.key.name is not saying that a < b.
They actually will be compared.
Try to replace it with return -1; to say to comparator:
a is lesser than b in that case.
When you use .sort() methods, you have to pass a compare function which will say who should comes first between two elements.
When you are comparing numbers, you can simply do :
function compare(a, b) {
return a - b;
}
this will sort an array of numbers ascending.
However, when you are comparing string, you have to define some ordering criterion, in order to tell which element should be comes first.
If compare(a, b) is less than 0, sort a to a lower index than b, so a comes first.
If compare(a, b) is greater than 0, sort b to a lower index than a, so b comes first.
If compare(a, b) is equal to 0, there is no change
So will get something like :
function compare(a, b) {
if (a is lower than b by your criteria) {
return -1;
}
if (a is greater than b by your criteria) {
return 1;
}
return 0;
}
In your case, you can write function generator that takes the property of the object to sort, and a custom sort function. This is useful when the function needs to be applied in more than one situation.
const username = '5';
const response = [
{"key":{"name":"1","kind":"a"}},
{"key":{"name":"1","kind":"ap"}},
{"key":{"name":"5","kind":"ap"}}
];
//Retrieve the value at 'props' into 'obj'
function retrieve(props, obj) {
return props.reduce((result, current) => {
result = result[current];
return result;
}, obj);
}
//Custom sort function for Kind properties
function sortKind(a, b) {
return a < b ? -1 : (a > b) ? 1 : 0;
}
//Custom sort function for name properties
function sortName(a, b) {
return a === username ? -1 : 1;
}
//Generic sort function
function sortByProp(...props) {
const callback = props.pop();
return function(a, b) {
const v1 = retrieve(props, a);
const v2 = retrieve(props, b);
return callback(v1, v2);
}
}
//Then you just have to call your sort function, and you can chain them
const res = response
.sort(sortByProp('key', 'kind', sortKind))
.sort(sortByProp('key', 'name', sortName));
console.log(res);
You can see here a Working plunker
You missed "return" in this line
else if(a.key.name == username){ a.key.name < b.key.name }
Related
I want to sort array by timestamp, but getting unsorted error
var userValue = {
timestamp,
username,
token,
userid,
message
};
userArray.push(userValue);
this is the array i am getting
userArray.sort(function(a, b) {
return a[0]['timestamp'] - b[0]['timestamp'];
});
i want sorted array on timestamp basis
This code
userArray[childDatamsg.timestamp] = userValue;
adds an object at timestamp index. This is why you have an array with the length of 1563533788! Instead push the userValue object to the userArray
userArray.push(userValue);
Now, it will have indices from 0.
Then you can sort the array like this:
userArray.sort((a, b) => a.timestamp - b.timestamp)
The following code sorts by the numerical value of timestamps. It ignores skips over array entries and performs string-to-number conversion of timestamps, if necessary. It assumes that besides 'string' and 'number', timstamps are of no other data type.
userArray.filter ( px_item => {
return (px_item !== undefined);
})
.map ( px_item => {
if (typeof px_item === "string") {
return parseInt(px_item);
} else {
return px_item;
}
})
.sort(function(a, b) {
if (typeof a === "undefined") {
return -1;
} else {
if (typeof b === "undefined") {
return 1;
} else {
return Math.sign ( a['timestamp'] - b['timestamp'] );
}
}
});
The original code had the sort function wrong. This function is actually a comparison function to determine the relative order of two elements (which in the given use case would be array entries). The order is expressed as one of the numerical values -1 (a < b), 0 (a = b), and 1 ( a > b ) (in fact, for the result of the comparison to be processed correctly, it suffices that the result has the proper sign, so Math.sign could be eliminated).
I have an array of strings I need to sort in JavaScript, but in a case-insensitive way. How to perform this?
In (almost :) a one-liner
["Foo", "bar"].sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
Which results in
[ 'bar', 'Foo' ]
While
["Foo", "bar"].sort();
results in
[ 'Foo', 'bar' ]
It is time to revisit this old question.
You should not use solutions relying on toLowerCase. They are inefficient and simply don't work in some languages (Turkish for instance). Prefer this:
['Foo', 'bar'].sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'}))
Check the documentation for browser compatibility and all there is to know about the sensitivity option.
myArray.sort(
function(a, b) {
if (a.toLowerCase() < b.toLowerCase()) return -1;
if (a.toLowerCase() > b.toLowerCase()) return 1;
return 0;
}
);
EDIT:
Please note that I originally wrote this to illustrate the technique rather than having performance in mind. Please also refer to answer #Ivan Krechetov for a more compact solution.
ES6 version:
["Foo", "bar"].sort(Intl.Collator().compare)
Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collator/compare
arr.sort(function(a,b) {
a = a.toLowerCase();
b = b.toLowerCase();
if (a == b) return 0;
if (a > b) return 1;
return -1;
});
You can also use the new Intl.Collator().compare, per MDN it's more efficient when sorting arrays. The downside is that it's not supported by older browsers. MDN states that it's not supported at all in Safari. Need to verify it, since it states that Intl.Collator is supported.
When comparing large numbers of strings, such as in sorting large arrays, it is better to create an Intl.Collator object and use the function provided by its compare property
["Foo", "bar"].sort(Intl.Collator().compare); //["bar", "Foo"]
If you want to guarantee the same order regardless of the order of elements in the input array, here is a stable sorting:
myArray.sort(function(a, b) {
/* Storing case insensitive comparison */
var comparison = a.toLowerCase().localeCompare(b.toLowerCase());
/* If strings are equal in case insensitive comparison */
if (comparison === 0) {
/* Return case sensitive comparison instead */
return a.localeCompare(b);
}
/* Otherwise return result */
return comparison;
});
Normalize the case in the .sort() with .toLowerCase().
You can also use the Elvis operator:
arr = ['Bob', 'charley', 'fudge', 'Fudge', 'biscuit'];
arr.sort(function(s1, s2){
var l=s1.toLowerCase(), m=s2.toLowerCase();
return l===m?0:l>m?1:-1;
});
console.log(arr);
Gives:
biscuit,Bob,charley,fudge,Fudge
The localeCompare method is probably fine though...
Note: The Elvis operator is a short form 'ternary operator' for if then else, usually with assignment.
If you look at the ?: sideways, it looks like Elvis...
i.e. instead of:
if (y) {
x = 1;
} else {
x = 2;
}
you can use:
x = y?1:2;
i.e. when y is true, then return 1 (for assignment to x), otherwise return 2 (for assignment to x).
The other answers assume that the array contains strings. My method is better, because it will work even if the array contains null, undefined, or other non-strings.
var notdefined;
var myarray = ['a', 'c', null, notdefined, 'nulk', 'BYE', 'nulm'];
myarray.sort(ignoreCase);
alert(JSON.stringify(myarray)); // show the result
function ignoreCase(a,b) {
return (''+a).toUpperCase() < (''+b).toUpperCase() ? -1 : 1;
}
The null will be sorted between 'nulk' and 'nulm'. But the undefined will be always sorted last.
arr.sort(function(a,b) {
a = a.toLowerCase();
b = b.toLowerCase();
if( a == b) return 0;
if( a > b) return 1;
return -1;
});
In above function, if we just compare when lower case two value a and b, we will not have the pretty result.
Example, if array is [A, a, B, b, c, C, D, d, e, E] and we use the above function, we have exactly that array. It's not changed anything.
To have the result is [A, a, B, b, C, c, D, d, E, e], we should compare again when two lower case value is equal:
function caseInsensitiveComparator(valueA, valueB) {
var valueALowerCase = valueA.toLowerCase();
var valueBLowerCase = valueB.toLowerCase();
if (valueALowerCase < valueBLowerCase) {
return -1;
} else if (valueALowerCase > valueBLowerCase) {
return 1;
} else { //valueALowerCase === valueBLowerCase
if (valueA < valueB) {
return -1;
} else if (valueA > valueB) {
return 1;
} else {
return 0;
}
}
}
In support of the accepted answer I would like to add that the function below seems to change the values in the original array to be sorted so that not only will it sort lower case but upper case values will also be changed to lower case. This is a problem for me because even though I wish to see Mary next to mary, I do not wish that the case of the first value Mary be changed to lower case.
myArray.sort(
function(a, b) {
if (a.toLowerCase() < b.toLowerCase()) return -1;
if (a.toLowerCase() > b.toLowerCase()) return 1;
return 0;
}
);
In my experiments, the following function from the accepted answer sorts correctly but does not change the values.
["Foo", "bar"].sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
This may help if you have struggled to understand:
var array = ["sort", "Me", "alphabetically", "But", "Ignore", "case"];
console.log('Unordered array ---', array, '------------');
array.sort(function(a,b) {
a = a.toLowerCase();
b = b.toLowerCase();
console.log("Compare '" + a + "' and '" + b + "'");
if( a == b) {
console.log('Comparison result, 0 --- leave as is ');
return 0;
}
if( a > b) {
console.log('Comparison result, 1 --- move '+b+' to before '+a+' ');
return 1;
}
console.log('Comparison result, -1 --- move '+a+' to before '+b+' ');
return -1;
});
console.log('Ordered array ---', array, '------------');
// return logic
/***
If compareFunction(a, b) is less than 0, sort a to a lower index than b, i.e. a comes first.
If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAscript standard does not guarantee this behaviour, and thus not all browsers (e.g. Mozilla versions dating back to at least 2003) respect this.
If compareFunction(a, b) is greater than 0, sort b to a lower index than a.
***/
http://jsfiddle.net/ianjamieson/wmxn2ram/1/
I wrapped the top answer in a polyfill so I can call .sortIgnoreCase() on string arrays
// Array.sortIgnoreCase() polyfill
if (!Array.prototype.sortIgnoreCase) {
Array.prototype.sortIgnoreCase = function () {
return this.sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
};
}
Wrap your strings in / /i. This is an easy way to use regex to ignore casing
I am trying to sort through arrays of n-dimensional objects based on a certain index. For example if I have an array:
var array1 = [['c',24,"John"],['a',-13,"Cindy"],['b',98,"Alex"]];
I want to be able to sort by any dimension. Does anything exist in javascript that would give me outputs that look like the following?
array1.sort(sortFunction(0));
- [['a',-13,"Cindy"],['b',98,"Alex"],['c',24,"John"]]
array1.sort(sortFunction(1));
- [['a',-13,"Cindy"],['c',24,"John"],['b',98,"Alex"]]
array1.sort(sortFunction(2));
- [['b',98,"Alex"],['a',-13,"Cindy"],['c',24,"John"]]
Thanks!
To clarify: I am aware you must write a sort function, but can you write one that takes in an argument? Here would be an example:
function sortFunction(dim){
if (a[dim] === b[dim]) {
return 0;
}
else {
return (a[dim] < b[dim]) ? -1 : 1;
}
}
You can write a function generator that take the index of the parameters in your array :
var array = [['a',24,"John"],['a',-13,"Cindy"],['b',98,"Alex"]];
function sorting(index) {
return function(a, b) {
return a[index] < b[index]?-1:(a[index] > b[index])?1:0;
}
}
var res1 = array.sort(sorting(0));
var res2 = array.sort(sorting(1));
var res3 = array.sort(sorting(2));
Note: this is the basic approach of how the sort callback function works. For a more elegant solution see Paul Boute’s answer. His solution is especially useful when the function needs to be applied in more than one situation.
Your sort function needs to compare two properties of the elements (or the two elements) themselves. It needs to return a negative number if they are in the correct order, 0 if they are equivalent and a positive number if they are in the wrong order.
A JavaScript example of such a function:
function sortFunction(elementA,elementB,index){
if(elementA[index]<elementB[index]){
return -1;
}
else if(elementA[index]>elementB[index]){
return 1;
}
else{
return 0;
}
}
Invokable as such:
array1.sort(function(a,b){
return sortFunction(a,b,1);
});
Or the shorter all-in-one version:
array1.sort(function(a,b){
var index=1;
if(a[index]<b[index]){
return -1;
}
else if(a[index]>b[index]){
return 1;
}
else{
return 0;
}
});
You can also use a library like underscore.js. Then, simply
// which index to sort on
var s = 1;
// sort it - it will work for objects too
_.sortBy(array1, function(a){ return a[s];});
// or simply use "property name" to sort on
_.sortBy( array1, s);
I am trying to write a function to sort my column because it has specific values. I have a lot alarms, and they have 2 states: Active and Not Active. And in case of "Active" I write to column string - "Active" and in case of "Not active" I write to column last seen date like: 24 Jun 2014, 07:36:14.
And the problem that when I use default jqgrid sort function, it sorts unproperly, because I have 2 types data.
And I found that in jqgrid I can write custom sorting functions. So here is my efforts:
var timeRegexPatter = /(\d{2})-(\d{3})-(\d{4}) (\d{2}):(\d{2}):(\d{2})/;
var alarmLastSeenSort = function(a, b, direction) {
if (a === 'Active') { //how to use directions properly?
return 1;
}
else if (b === 'Active') {
return -1;
}
var timeArrA = a.match(timeRegexPatter); //array A
var timeArrB = b.match(timeRegexPatter); //Array B
//here should be probably transform time regex into timestamp and compare it,
//is there any predefined functions to do this
}
Caveat: I don't know the jqgrid library specifically.
In general, though, a sort function should return 1, 0, or -1 based on the comparison of the two incoming arguments. Assuming an ascending sort:
If a < b, return -1
If a == b, return 0
If a > b, return 1
where the <, ==, and > operators refer to your desired collating order of the objects, which may not necessarily be the same as strict mathematical or string comparisons. For example, you may have an object that you want to sort by name, then ID, which would involve comparisons of two different properties with different types.
In your case, you have two axes on which to sort, "activeness" and "timestamp". So your first question is: how should an active compare to an inactive? It doesn't make sense to compare one item at a time, unless it's to forbid sorting of objects that are not the same type and throw an error.
Once you dispose of the "activeness" sort, you can move on to comparing timestamps in the case of an inactive item.
Again, I don't know jqgrid specifically, but I assume direction refers to "ascending" or "descending" order. That will determine whether you return a 1 (ascending) or -1 (descending) for the a > b case.
Demo here.
var i, sortedArray;
var testArray = [
'active',
'2014-06-25 01:23:45',
'active',
'active',
'2013-01-31 12:34:56',
'2014-06-25 02:34:45'];
var comparitor = function(a, b, direction) {
console.log('comparitor(a, b, direction)::', a, b, direction);
// Replace this with whatever test allows you to determine whether you're sorting in ascending or descending order
var after = direction === 'descending' ? -1 : 1;
// If both are active, neither should be reordered with respect to the other
if (a === 'active' && b === 'active') {
console.log('Both a & b are active; returning 0');
return 0;
}
// We know at least one is "inactive". Assume "active" should come before "inactive".
if (a === 'active') {
console.log('a is active; returning -1');
return -1 * after;
} else if (b === 'active') {
console.log('b is active; returning 1');
return after;
}
// We know that neither one is active, and can assume both are date strings. You could convert to dates here, but why, since your dates are already in a format that sorts quite nicely?
if (a === b) {
console.log('a === b; returning 0');
return 0;
}
console.log('a !== b; returning either 1 or -1');
return a > b ? after : -1 * after;
}
sortedArray = testArray.sort(comparitor);
for (i = 0; i < sortedArray.length; i++) {
console.log(i + ' = ' + sortedArray[i]);
}
Background:
As needed in some task, I need a simple sort function. For simplicity, I wrote another function to wrap the built-in sort function as:
function sortBy(obj, extra, func){
if(typeof func == 'function'){
f = func;
} else if(typeof extra != 'function'){
eval('function f(a, b, ai, bi, e){return ' + func + '}');
} else {
var f = extra;
extra = null;
}
var res = [];
for(var i in obj){
if(obj.hasOwnProperty(i)){
obj[i]._k_ = i;
res.push(obj[i]);
}
}
res.sort(function(a, b){
if(f(a, b, a._k_, b._k_, extra)){
return 1;
} else {
return -1;
}
})
return res;
}
My attempts are:
Make it possible to sort a object directly
Keep the original object as the hash table
Allow some simple syntax
For instance,
var data ={
12: {age:27, name:'pop', role: 'Programmer'},
32: {age:25, name:'james', role: 'Accontant'},
123:{age:19, name:'jerry', role:'Sales Representative'},
15:{age:22, name:'jerry', role:'Coder'},
17:{age:19, name:'jerry', role:'Tester'},
43:{age:14, name:'anna', role: 'Manager'},
55: {age:31, name:'luke', role:'Analyst'}
};
There are several usages:
var b = sortBy(data, '', 'a.age < b.age'); // a simple sort, order by age
var b = sortBy(data, 19, 'b.age == e'); // pick up all records of age 19, and put them in the beginning
var b = sortBy(data, function(a, b){return a.name > b.name}); // anonymous sort function is also allowed
QUESTION
Though it works as expected in our code, I would like to raise some question:
Is there any potiential problem about using eval to create sort function from string?
Is there any story about sort function returning -1(nagative), 0 and 1(positive)?
Can we change the code as "return if(f(a, b, a.k, b.k, extra)", instead of returning 1 or -1? We found it works in our firefox and chrome, but not sure whether it is safe to do so.
1. Is there any potiential problem about using eval to create sort function from string?
Not per se, but it does suffer all the same deficiencies as calling other eval-style functions with strings, e.g. setTimeout() or the Function() constructor. As long as you trust the source of the string there's no real problem. However, I would consider using the Function constructor instead:
f = new Function(a, b, ai, bi, e, 'return ' + func);
It's more manageable and it's definitely more appropriate than evaluating a function declaration.
2. Is there any story about sort function returning -1(nagative), 0 and 1(positive)?
Not really understanding this part of your question, but your function doesn't appear to tackle what to do if two items are the same from the comparison. You should be returning less than 0, 0 or more than 0 depending on the result. The best approach for this is to use String.prototype.localeCompare():
return String.prototype.localeCompare.call(a, b);
Try making it so you only do eval on a small part:
(fiddle: http://jsfiddle.net/maniator/SpJbN/)
function sortBy(obj, extra, func){
var f = null;
if(typeof func == 'function'){
f = func;
} else if(typeof extra != 'function'){
f = function(a, b, ai, bi, e){
return eval(func); // <-- smaller part
}
} else {
f = extra;
extra = null;
}
var res = [];
for(var i in obj){
if(obj.hasOwnProperty(i)){
obj[i]._k_ = i;
res.push(obj[i]);
}
}
res.sort(function(a, b){
if(f(a, b, a._k_, b._k_, extra)){
return 1;
} else {
return -1;
}
})
return res;
}
Thanks to Andy, we change the code to
var f = new Function('a, b, ai, bi, e', 'return ' + func);
Note that the arguments should be passed in as string, check out :
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function
About the second question, I think it is because we were trying to make the sort function more explicitly.
For instance,
sort([1, 2, 3, 5, 1], '', 'a < b') ==> [1, 1, 2, 3, 5]
Literal meaning of 'a < b' to us is "the latter item is greater than the former item", so the array is sorted to [1, 1, 2, 3, 5].
Another example, 'a.age < b.age' will return the records in the order that younger person comes before olders.
That's the reason I am asking can we use true or false instead of -1, 0, 1.
We keep doing some more small tests, and figure out something, would like to share with everyone.
For example:
var b = [
{age:27, name:'pop 2', role: 'Programmer'},
{age:19, name:'james', role: 'Accontant'},
{age:19, name:'jerry', role:'Sales Representative'},
{age:22, name:'jerry', role:'Coder'},
{age:19, name:'jerry', role:'Tester'},
{age:14, name:'anna', role: 'Manager'},
{age:19, name:'luke', role:'Analyst'},
{age:27, name:'pop', role: 'Programmer'},
{age:14, name:'anna 2', role: 'Manager'}
];
b.sort(function(a, b) {return a.age - b.age > 0? 1: -1}); // #1
b.sort(function(a, b) {return a.age > b.age}); // #2
b.sort(function(a, b) {return a.age - b.age}); // #3
Although the above sorts return the same result, try this one :
b.sort(function(a, b) {return a.age - b.age < 0? -1: 1}); // #4
In this statement, the records are still ordered by age, but the order is reversed within a same age group.
#1 and #2 is same as #3 by chance. If the browser use different algorithm to implement the sort function,
it is possible that #1 and #2 will behave like #4. If you are strict with the order of the result, you need
to explicitly return 0, when 'a' equals to item 'b'.
Besides, as Andy pointed out, in certain cases(e.g., #4), it is possible that unnecessary swaps are done if we don't return 0 explicitly, which could affect the performance.
The reason we didn't notice this before is because we didn't care the order within a group, providing the record
is sorted on certain property.
I think it would be appropriate to pass a function that does the comparing. so something like
sort(somedata, function(a, b) {
return a < b;
});