Related
Im new to react js and im trying to sort the dates...the problem is the dates have null value and i want the null dates to always appear at the last irrespective of the order('asc' or 'desc')....i have seen so many examples in stack overflow where they are using customsort by checking with the order...whether its asc or desc....im using materialtable and im unable to get hold of the sortingorder....how can i gethold of the sortingorder so i can implement the customsorting where the null dates always appear at the last....irrespective of asc or desc
i have tried
customSort:(a,b)=>
{
return (b.paymentDate != null) - (a.paymentDate != null) || a.paymentDate - b.paymentDate ;
}
this type....on my columns....but its working only for one way.....i want to gethold of the order...so i can write it according to asc or desc
Based on your example, it looks like your customSort function is expected to behave like Array.sort()'s compare function.
Additionally, it looks like the customSort function gets passed the string "desc" as a 4th arg if the order is descending:
if (columnDef.customSort) {
if (this.orderDirection === "desc") {
result = list.sort((a, b) => columnDef.customSort(b, a, "row", "desc"));
} else {
result = list.sort((a, b) => columnDef.customSort(a, b, "row"));
So you can add a 4th param to your customSort, and handle the sorting of null values differently based on it; taking into account that the caller swaps a and b if order == 'desc'. What about:
customSort: (a, b, type, order) => {
let desc = order == "desc";
if (a.paymentDate !== null && b.paymentDate !== null)
return a.paymentDate - b.paymentDate;
else if (a.paymentDate !== null)
return desc ? 1 : -1;
else if (b.paymentDate !== null)
return desc ? -1 : 1;
else
return 0;
}
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 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 }
Update 2
I added a weight lookup to the sort function, which increased the performance by about 100% as well as the stability, as the previous sort function didn't consider all types, and as 1 == "1" the result depends on the initial order of the Array, as #Esailija points out.
The intent of the question is to improve this Answer of mine, I liked the question and since it got accepted and I felt like there is some performance to squeeze out of the sort function. I asked this question here since I hadn't many clues left where to start.
Maybe this makes things clearer as well
Update
I rephrased the complete question, as many people stated I was not specific enough, I did my best to specify what I mean. Also, I rewrote the sort function to better express the intent of the question.
Let arrayPrev be an Array (A) ,where A consists of 0 to n Elements' (E)
Let an Element either be
of a Primitive type
boolean, string, number, undefined, null
a Reference to an Object O, where O.type = [object Object] and O can consist of
0 to n Properties P, where P is defined like Element plus
an circular Reference to any P in O
Where any O can be contained 1 to n times. In the sense of GetReferencedName(E1) === GetReferencedName(E2)...
a Reference to an O, where O.type = [object Array] and O is defined like A
a circular Reference to any E in A
Let arrayCurr be an Array of the same length as arrayPrev
Illustrated in the following example
var x = {
a:"A Property",
x:"24th Property",
obj: {
a: "A Property"
},
prim : 1,
}
x.obj.circ = x;
var y = {
a:"A Property",
x:"24th Property",
obj: {
a: "A Property"
},
prim : 1,
}
y.obj.circ = y;
var z = {};
var a = [x,x,x,null,undefined,1,y,"2",x,z]
var b = [z,x,x,y,undefined,1,null,"2",x,x]
console.log (sort(a),sort(b,a))
The Question is, how can I efficiently sort an Array B, such that any Reference to an Object or value of a Primitive, shares the exact same Position as in a Previously, by the same compareFunction, sorted, Array A.
Like the above example
Where the resulting array shall fall under the rules.
Let arrayPrev contain the Elements' of a and arrayCurr contain the Elements' of b
Let arrayPrev be sorted by a CompareFunction C.
Let arrayCurr be sorted by the same C.
Let the result of sorting arrayCur be such, that when accessing an E in arrayCur at Position n, let n e.g be 5
if type of E is Object GetReferencedName(arrayCurr[n]) === GetReferencedName(arrayPrev[n])
if type of E is Primitive GetValue(arrayCurr[n]) === GetValue(arrayPrev[n])
i.e b[n] === a[n] e.g b[5] === a[5]
Meaning all Elements should be grouped by type, and in this sorted by value.
Where any call to a Function F in C shall be at least implemented before ES5, such that compatibility is given without the need of any shim.
My current approach is to Mark the Objects in arrayPrev to sort them accordingly in arrayCurr and later delete the Marker again. But that seems rather not that efficient.
Heres the current sort function used.
function sort3 (curr,prev) {
var weight = {
"[object Undefined]":6,
"[object Object]":5,
"[object Null]":4,
"[object String]":3,
"[object Number]":2,
"[object Boolean]":1
}
if (prev) { //mark the objects
for (var i = prev.length,j,t;i>0;i--) {
t = typeof (j = prev[i]);
if (j != null && t === "object") {
j._pos = i;
} else if (t !== "object" && t != "undefined" ) break;
}
}
curr.sort (sorter);
if (prev) {
for (var k = prev.length,l,t;k>0;k--) {
t = typeof (l = prev[k]);
if (t === "object" && l != null) {
delete l._pos;
} else if (t !== "object" && t != "undefined" ) break;
}
}
return curr;
function sorter (a,b) {
var tStr = Object.prototype.toString
var types = [tStr.call(a),tStr.call(b)]
var ret = [0,0];
if (types[0] === types[1] && types[0] === "[object Object]") {
if (prev) return a._pos - b._pos
else {
return a === b ? 0 : 1;
}
} else if (types [0] !== types [1]){
return weight[types[0]] - weight[types[1]]
}
return a>b?1:a<b?-1:0;
}
}
Heres a Fiddle as well as a JSPerf (feel free to add your snippet)
And the old Fiddle
If you know the arrays contain the same elements (with the same number of repetitions, possibly in a different order), then you can just copy the old array into the new one, like so:
function strangeSort(curr, prev) {
curr.length = 0; // delete the contents of curr
curr.push.apply(curr, prev); // append the contents of prev to curr
}
If you don't know the arrays contain the same elements, it doesn't make sense to do what you're asking.
Judging by the thing you linked, it's likely that you're trying to determine whether the arrays contain the same elements. In that case, the question you're asking isn't the question you mean to ask, and a sort-based approach may not be what you want at all. Instead, I recommend a count-based algorithm.
Compare the lengths of the arrays. If they're different, the arrays do not contain the same elements; return false. If the lengths are equal, continue.
Iterate through the first array and associate each element with a count of how many times you've seen it. Now that ES6 Maps exist, a Map is probably the best way to track the counts. If you don't use a Map, it may be necessary or convenient to maintain counts for items of different data types differently. (If you maintain counts for objects by giving them a new property, delete the new properties before you return.)
Iterate through the second array. For each element,
If no count is recorded for the element, return false.
If the count for the element is positive, decrease it by 1.
If the count for the element is 0, the element appears more times in the second array than in the first. Return false.
Return true.
If step 4 is reached, every item appears at least as many times in the first array as it does in the second. Otherwise, it would have been detected in step 3.1 or 3.3. If any item appeared more times in the first array than in the second, the first array would be bigger, and the algorithm would have returned in step 1. Thus, the arrays must contain the same elements with the same number of repetitions.
from the description, it appears you can simply use a sort function:
function sortOb(a,b){a=JSON.stringify(a);b=JSON.stringify(b); return a===b?0:(a>b?1:-1);}
var x = {a:1};
var y = {a:2};
var a = [1,x,2,3,y,4,x]
var b = [1,y,3,4,x,x,2]
a.sort(sortOb);
b.sort(sortOb);
console.log (a,b);
Your function always returns a copy of sort.el, but you can have that easier:
var sort = (function () {
var tmp;
function sorter(a, b) {
var types = [typeof a, typeof b];
if (types[0] === "object" && types[1] === "object") {
return tmp.indexOf(a) - tmp.indexOf(b); // sort by position in original
}
if (types[0] == "object") {
return 1;
}
if (types[1] == "object") {
return -1;
}
return a > b ? 1 : a < b ? -1 : 0;
}
return function (el) {
if (tmp) {
for (var i = 0; i < el.length; i++) {
el[i] = tmp[i]; // edit el in-place, same order as tmp
}
return el;
}
tmp = [].slice.call(el); // copy original
return tmp = el.sort(sorter); // cache result
};
})();
Note that I replaced sort.el by tmp and a closure. Fiddle: http://jsfiddle.net/VLSKK/
Edit: This (as well as your original solution)
only works when the arrays contain the same elements.
maintains the order of different objects in a
but
does not mess up when called more than twice
Try this
function ComplexSort (a, b)
{
if(typeof a == "object") {
a = a.a;
}
if(typeof b == "object") {
b = b.a;
}
return a-b;
}
var x = {a:1};
var y = {a:2};
var a = [1,x,2,3,y,4,x]
var b = [1,y,3,4,x,x,2]
a.sort(ComplexSort);
b.sort(ComplexSort);
console.log ("Consistent", a,b);
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;
});