How to sort in javascript - javascript

If I have an array like this:
var tab = ['1185 Design','3 D Exhibits','44Doors', '4Concepts','ABC Data','acceleration'];
And I want to sort it so that small letter 'a' element comes before capital letter 'A' element.

Use Array#sort() method with String#localeCompare()
var tab = ['1185 Design', '3 D Exhibits', 'nb', 'N', 'cd', '44Doors', '4Concepts', 'ABC Data', 'acceleration'];
tab.sort(function(a, b) {
return sortFn(a, b);
});
function sortFn(a, b) {
// if both are equal return 0
if (a == b) return 0;
// if first characters are equal call the same function with remaining (recursion)
if (a.charAt(0) == b.charAt(0)) return sortFn(a.slice(1), b.slice(1))
// check lowercase or uppercase based on that return value
if (/^[a-z]/.test(a.charAt(0)) && /^[A-Z]/.test(b.charAt(0))) return -1;
if (/^[a-z]/.test(b.charAt(0)) && /^[A-Z]/.test(a.charAt(0))) return 1;
// otherwise ude normal compare function
return a.localeCompare(b);
}
console.log(tab);
UPDATE : In case if you want to sort with alphabetical order and small letter should have higher precedence only if they are equal then do something like.
var tab = ['1185 Design', '3 D Exhibits', 'nb', 'N', 'cd', '44Doors', '4Concepts', 'ABC Data', 'acceleration'];
tab.sort(function(a, b) {
return sortFn(a, b);
});
function sortFn(a, b) {
// if both are equal return 0
if (a == b) return 0;
// if first characters are equal call the same function with remaining (recursion)
if (a.charAt(0) == b.charAt(0)) return sortFn(a.slice(1), b.slice(1))
// check lowercase or uppercasebased on that return value in case the letters are equal
if (a.charAt(0).toLowerCase() == b.charAt(0).toLowerCase()) {
if (/^[a-z]/.test(a.charAt(0)) && /^[A-Z]/.test(b.charAt(0))) return -1;
if (/^[a-z]/.test(b.charAt(0)) && /^[A-Z]/.test(a.charAt(0))) return 1;
}
// otherwise ude normal compare function
return a.localeCompare(b);
}
console.log(tab);

You could use sorting with map with a reversion of case.
// the array to be sorted
var list = ['1185 Design', '3 D Exhibits', '44Doors', '4Concepts', 'ABC Data', 'acceleration'];
// temporary array holds objects with position and sort-value
var mapped = list.map(function (el, i) {
return { index: i, value: el.split('').map(function (a) {
var b = a.toUpperCase();
return a === b ? a.toLowerCase(): b;
}).join('')};
});
// sorting the mapped array containing the reduced values
mapped.sort(function (a, b) {
return +(a.value > b.value) || +(a.value === b.value) - 1;
});
// container for the resulting order
var result = mapped.map(function (el) {
return list[el.index];
});
console.log(result);

Related

Why does my custom sort() function cannot be accepted in JavaScript?

I tried to upgrade the current custom sort function of JavaScript to create a new order of sorting e.g. (1, 2, 3, 4,..., !##$%^=+, a, A, b, B, c, C)
function getSortOrder(prop) {
return function (a, b) {
if (isSpecialChar(a[prop], 0) || isSpecialChar(b[prop], 0)) {
return sortData(a[prop], b[prop]);
}
if (isNumeric(a[prop], 0) == "number" || isNumeric(b[prop], 0) == "number") {
return getSortNumeric(a[prop], b[prop]);
}
if (isLetter(a[prop], 0) || isLetter(b[prop], 0)) {
return getSortLetter(a[prop], b[prop]);
}
};
}
function getSortLetter(a, b) {
if ((a.charAt(0) === getLowerCase(a, 0)) && (b.charAt(0) === getUpperCase(b, 0))) {
return sortData(a, b);
}
return sortData(a, b);
}
function getSortNumeric(a, b) {
if (typeof a[prop] == "number") {
return (a[prop] - b[prop]);
} else {
return ((a[prop] < b[prop]) ? -1 : ((a[prop] > b[prop]) ? 1 : 0));
}
}
function sortData(a, b) {
if (a.toLowerCase() < b.toLowerCase()) {
return -1;
} else if (a.toLowerCase() > b.toLowerCase()) {
return 1;
} else {
return 0;
}
}
/**
* Function that is used for the ascending order of number
*
*/
const sortNumberData = (a, b) => a.localeCompare(b, 'en', { numeric: true })
// to check if the data has numeric
function isNumeric(str, index) {
let x = /^[0-9]$/.test(str.charAt(index));
console.log(str, x);
return x;
}
// to determine if the data has neither numeric or letter
function isSpecialChar(str, index) {
return !isNumeric(str, index) && !isLetter(str, index);
}
// to specify the order of letter e.g. (jane doe, Jane Doe, john doe, John doe)
function isLetter(str, index) {
return str.charAt(index).length === 1 && str.match(/[a-z]/i);
}
function getLowerCase(str, index) {
return str.charAt(index).toLowerCase();
}
function getUpperCase(str, index) {
return str.charAt(index).toUpperCase();
}
expected result of Json Values:
List of Users:
123Admin
321user
!testAdmin
#adminData
jane doe
Jane Smith
john doe
John Doe
Current results of Json Values:
List of Users:
!testAdmin
#adminData
123Admin
321user
Jane Smith
jane doe
john doe
It still follows the Ascii default order of sort.
The approach suggested by Nina Scholz is more concise, but here is what was wrong with your original code:
Your isLetter function does not return the correct result. Using the RegExp.test method as below would fix that:
function isLetter(str, index) {
return str.charAt(index).length === 1 && /^[a-z]/i.test(str);
}
Your getSortOrder function also does not handle sorting correctly when comparing characters that belong to different groups (special character / number / letter). To fix that, you could change that function to distinguish when the characters are in the same group versus when they are in different groups:
function getSortOrder(a, b) {
if (isNumeric(a, 0) && isNumeric(b, 0)) return sortData(a, b);
if (isSpecialChar(a, 0) && isSpecialChar(b, 0)) return sortData(a, b);
if (isLetter(a, 0) && isLetter(b, 0)) return sortData(a, b);
if (isNumeric(a, 0)) return -1;
if (isLetter(a, 0)) return 1;
if (isSpecialChar(a, 0)) {
if (isNumeric(b, 0)) return 1;
return -1;
}
}
Finally, the sortData function does not distinguish between lower and upper case. It would need to do something like this:
function sortData(a, b) {
const aLower = a[0].toLowerCase();
const bLower = b[0].toLowerCase();
if (aLower === bLower) {
if (a[0] === aLower && b[0] !== bLower) return -1;
if (a[0] !== aLower && b[0] === bLower) return 1;
return 0;
}
if (aLower < bLower) return -1;
if (aLower > bLower) return 1;
return 0;
}
You could take a brute force approach with an string/object for the wanted order.
This appriach iterate each pair of strings and check any character by getting the order until finding different characters.
const
chars = ' 0123456789!##$%^=+abcdefghijklmnopqrstuvwxyz',
order = Object.fromEntries(Array.from(chars, ((c, i) => [c, i + 1]))),
sort = (a, b) => {
for (let i = 0, l = Math.min(a.length, b.length); i < l; i++) {
const r = order[a[i].toLowerCase()] - order[b[i].toLowerCase()];
if (r) return r;
}
return a.length - b.length;
},
sortBy = (fn, k) => (a, b) => fn(a[k], b[k]),
data = [{ name: 'abcd' }, { name: 'abc' }, { name: 'John Doe' }, { name: '!testAdmin' }, { name: '#adminData' }, { name: '123Admin' }, { name: '321user' }, { name: 'Jane Smith' }, { name: 'jane doe' }, { name: 'john doe' }];
data.sort(sortBy(sort, 'name'));
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's a function that can be used in a sort.
It starts with finding the index of the first uncommon character between the lower case strings.
Then assigns the order (-1,0,+1) depending on a priority, and then the order of the lower case strings.
function newSort(a, b) {
let lca = a.toLowerCase();
let lcb = b.toLowerCase();
let len = Math.min(a.length, b.length);
let i = 0;
// find index of first uncommon character
while(lca[i] === lcb[i] && i<len) i++;
// what priority do the types of the uncommon character get
let prioA = !lca[i] ? 0 : /^\d/.test(lca[i]) ? 1 : /^[a-z]/.test(lca[i]) ? 3 : 2;
let prioB = !lcb[i] ? 0 : /^\d/.test(lcb[i]) ? 1 : /^[a-z]/.test(lcb[i]) ? 3 : 2;
let order = prioA > prioB ? 1 : prioA < prioB ? -1
: lca > lcb ? 1 : lca < lcb ? -1 : 0;
return order
}
const stringArray = [
"1!a", "1a!", "!1a", "!a1", "a!1", "a1!"
, "Jane Smith" , "jane doe" , "john doe"
, "abcX", "ABC", "DEFy", "defx"
];
let sortedStringArray = stringArray.sort(newSort);
console.log(sortedStringArray);

JS compare the order of SOME elements in two different arrays

I'm trying to figure out how to compare if certain elements in two arrays compare in the same order.
var compare = function (arr1, arr2) {
//........
}
compare ([f,t,r,m], [s,f,t,r,q,p,m]); //should return true
compare ([f,t,r,m], [f,a,t,,m,r]); //should return false
I proceeded with a for loop and then identifying when the values match, then I'm pretty sure you should compare the arrays but I feel I'm missing something.
var compare = function (a, b) {
a.forEach(function(letter){
for (i=0; i<b.length; i++) {
if (letter===b[i]) {}
}
})
}
Both of these functions will do this comparison with a O(n) runtime, where Ori Drori's solution runs in O(n^2)
var a = [1,2,3];
var b = [0,1,4,3,9,10,2,5,6]; // 1,2,3 in wrong order
var c = [0,4,1,5,6,2,8,3,5]; // 1,2,3 in right order
// using foreach
function compare(a,b){
var i = 0;
b.forEach(function(el){
if(el == a[i]) i++;
})
return i == a.length;
}
// using reduce
function compare2(a,b){
return b.reduce(function(i, el){
return el == a[i] ? i + 1 : i;
}, 0) == a.length;
}
console.log(compare(a,b) == false); // should be false
console.log(compare(a,c) == true); // should be true
console.log(compare2(a,b) == false); // should be false
console.log(compare2(a,c) == true); // should be true
Remove all letters from the 2nd array that don't appear in 1st array using Array#filter and Array#indexOf. Then iterate the result with Array#every, and check if every character appears in the same place in the 1st array:
function compare(a, b) {
var arr = b.filter(function(c) {
return a.indexOf(c) !== -1; // use a hash object instead of indexOf if the arrays are large
});
return arr.every(function(c, i) {
return c === a[i];
});
}
console.log(compare(['f','t','r','m'], ['s','f','t','r','q','p','m'])); //should return true
console.log(compare(['f','t','r','m'], ['f','a','t','m','r'])); //should return false
You could take an index for array2 and check while iterating and return the comparison of the index and array2 and the element of array1.
function compare(array1, array2) {
var i = 0;
return array1.every(function (a) {
while (i < array2.length && a !== array2[i]) {
i++;
}
return a === array2[i++];
});
}
console.log(compare(['f', 't', 'r', 'm'], ['s', 'f', 't', 'r', 'q', 'p', 'm'])); // true
console.log(compare(['f', 't', 'r', 'm'], ['f', 'a', 't', , 'm', 'r'])); // false
You may do as follows;
function compare(a,b){
return b.filter(e => a.includes(e))
.every((e,i) => e === a[i])
}
console.log(compare(["f","t","r","m"], ["s","f","t","r","q","p","m"]));
console.log(compare(["f","t","r","m"], ["f","a","t","m","r"]));

.sort() method not working

function isSorted(set) {
if(set == set.sort()) {
return true;
} else {
return false;
}
}
This function is supposed to check if an array is sorted properly, but no matter what, it still returns true.
From MDN:
The sort() method sorts the elements of an array in place and returns the array.
After set.sort() is invoked, set itself has been sorted, so set will always equal set.sort().
Reference:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
As pointed out in the comments, == compares by reference and hence still refers to the same array. Thereby the condition is always true
Use the below method to ensure if the array is sorted or not. Found the method form here
/*
* check the array is sorted
* return: if positive (ascending) -> 1
* if negative (descending) -> -1
* not sorted -> 0
*/
Array.prototype.isSorted = function() {
return (function(direction) {
return this.reduce(function(prev, next, i, arr) {
if (direction === undefined)
return (direction = prev <= next ? 1 : -1) || true;
else
return (direction + 1 ?
(arr[i-1] <= next) :
(arr[i-1] > next));
}) ? Number(direction) : false;
}).call(this);
}
var arr = [3,2,1,0];
arr.isSorted();
Or you can create a similar function like this using the above code
/*
* check the array is sorted
* return: if positive (ascending) -> 1
* if negative (descending) -> -1
* not sorted -> 0
*/
function isSorted(myArray) {
return (function(direction) {
return myArray.reduce(function(prev, next, i, arr) {
if (direction === undefined)
return (direction = prev <= next ? 1 : -1) || true;
else
return (direction + 1 ?
(arr[i-1] <= next) :
(arr[i-1] > next));
}) ? Number(direction) : false;
}).call(myArray);
}
var arr = [3,2,1,0];
isSorted(arr);
I suggest to use another way to check, if an array is sorted. You could use a compareFunction as described by Array#sort and use it as check for test as sorted with Array#every
For example, if you have numbers and a compareFunction like
var greaterOrEqual = function (a, b ) { return a - b; }
you could use the function to sort an array with number like
array.sort(greaterOrEqual);
and you could use the following pattern for checking every element
var isSorted = function (compareFunction) {
return function (a, i, aa) {
return !(compareFunction(aa[i - 1], a) > 0);
};
};
and then use it with an array, like
var isSorted = array.every(isSorted(greaterOrEqual));
A working example
var greaterOrEqual = function (a, b ) { return a - b; },
isSorted = function (compareFunction) {
return function (a, i, aa) {
return !(compareFunction(aa[i - 1], a) > 0);
};
};
console.log([42].every(isSorted(greaterOrEqual))); // true
console.log([42, 42].every(isSorted(greaterOrEqual))); // true
console.log([1, 2, 3, 4, 5].every(isSorted(greaterOrEqual))); // true
console.log([1, 2, 5, 4, 3].every(isSorted(greaterOrEqual))); // false
.as-console-wrapper { max-height: 100% !important; top: 0; }

Function sort an array with JavaScript

I have to write a function, which sorts an array containing numbers and strings.
For example:
uSort([3,"2", 4,1,"a","c","b"]) // -> ["a","b","c",1,"2",3, 4].
This is what I've tried so far:
function uSort(arrayOfChars){
var array = [];
for (i = 0; i < arrayOfChars.length; i++) {
if (typeof(arrayOfChars[i]) === '' ){
array.unshift(arrayOfChars[i]); }
else {
array.push(arrayOfChars[i]);
};
};
return array.sort();
};
But the result is wrong:
uSort([3,"2", 4,1,"a","c","b"]) // -> [1,"2",3, 4,"a","b","c"].
I can't figure out what is wrong with my code right now.
One easy way to do that would be to just split the array into two arrays, one containing numbers and strings that are numbers, using isNaN, and one array containing everything else, then sort them and join them back together
function uSort(arrayOfChars){
var numbs = arrayOfChars.filter(function(item) { return isNaN(item) });
var chars = arrayOfChars.filter(function(item) { return !isNaN(item) });
return numbs.sort().concat( chars.sort() );
};
FIDDLE
For better sorting of integers and special characters, you can add callbacks to the sorting
function uSort(arrayOfChars){
var numbs = arrayOfChars.filter(function(item) { return !isNaN(item) });
var chars = arrayOfChars.filter(function(item) { return isNaN(item) });
return chars.sort(function(a, b) {
return a.localeCompare(b);
}).concat( numbs.sort(function(a, b) {
return a == b ? 1 : a - b;
}));
};
FIDDLE
You can use a custom comparator function which checks if the arguments are numeric with isNaN and then uses numerical or lexicographic sort:
[3, "2", 4, 1, "a", "c", "b"].sort(function(a,b) {
if(isNaN(a) || isNaN(b)) {
if(!isNaN(a)) return +1; // Place numbers after strings
if(!isNaN(b)) return -1; // Place strings before numbers
return a < b ? -1 : (a > b ? +1 : 0); // Sort strings lexicographically
}
return a - b; // Sort numbers numerically
}); // ["a", "b", "c", 1, "2", 3, 4]
Write your own custom sort method.
[3,"2", 4,1,"a","c","b"].sort( function (a,b) {
var sa = isNaN(a);
var sb = isNaN(b);
if(sa && sb) { //If both are strings, than compare
return sa>sb;
} else if (!sa && !sb) { //if both are numbers, convert to numbers and compare
return Number(a) - Number(b);
} else { //if we have a number and a string, put the number last.
return sa ? -1 : 1;
}
});
It's normal to have numbers before letters (if this is your problem).
If you want to have letters before you have different ways.
One way is to order elements into two arrays (one for letters and one for numbers), then to merge them in the order you need.
Another way is to move the numbers at the end.
This is an example:
alert((uSort([3,"2", 4,10,"a","c","b"]))) // -> ["a","b","c",1,"2",3, 4].
function uSort(arrayOfChars){
var arrayNum = [];
var arrayLet = [];
for (i = 0; i < arrayOfChars.length; i++) {
if (typeof(arrayOfChars[i]) === '' ){
array.unshift(arrayOfChars[i]); }
else if (typeof(arrayOfChars[i]) === 'number' ){
arrayNum.push(arrayOfChars[i]);
} else {
arrayLet.push(arrayOfChars[i]);
}
};
if (arrayNum.size!=0 && arrayLet.size!=0) {
arrayNum = arrayNum.sort(sortNumber);
arrayNum.push();
return arrayNum.concat(arrayLet.sort());
} else if (arrayNum.size!=0) {
return arrayNum.sort(sortNumber);
} else {
return arrayLet.sort();
}
};
function sortNumber(a,b) {
return a - b;
}

How can I optimize this switch statement?

I try to use in my app, simple comparator to filter some data with passing string filter instead function as eg. passed to [].filter
Comparator should return function which will be a filter.
var comparator = function( a, b, c ) {
switch( b ){
case '>=': return function() { return this[a] >= c;}; break;
case '<=': return function() { return this[a] <= c;}; break;
case '<': return function() { return this[a] < c;}; break;
case '>': return function() { return this[a] > c;}; break;
case '=': return function() { return this[a] == c;}; break;
case '==': return function() { return this[a] === c;}; break;
case '!=': return function() { return this[a] != c;}; break;
default: return null;
};
}
Assume that i get this function by:
var filterFn = comparator.apply({}, /(.+)(=|>=|<=|<|>|!=|==|!==)(.+)/.exec( "id<4" ).slice(1) );
someModel = someModel.objects.filter( filterFn );
The target it will look:
someModel.get = function( filter ){
return new Model(
this.objects.filter(
comparator.apply({}, /(.+)(=|>=|<=|<|>|!=|==|!==)(.+)/.exec( "id<4" ).slice(1)
)
);
};
var filtered = someModel.get( "id<4" );
Question is - I assume that it will be a lot more operators and I have no idea how to write it more simply.
Using Eval is out of question.
This code didn't was both executed and tested I wrote it just to show what I mean.
Store every function in an object, either pre-defined, or dynamically.
If you want to dyanmically create the set of functions, define the comparator object as shown below. I assumed that you did not extend the Object.prototype. If you did, operators.hasOwnProperty(property) has to be used within the first loop.
// Run only once
var funcs = {}; // Optionally, remove `funcs` and swap `funcs` with `operators`
var operators = { // at the first loop.
'>=': '>=',
'<=': '<=',
'<' : '<',
'>' : '>',
'=' : '==', //!!
'==':'===', //!!
'!=': '!='
}; // Operators
// Function constructor used only once, for construction
for (var operator in operators) {
funcs[operator] = Function('a', 'c',
'return function() {return this[a] ' + operator + ' c};');
}
// Run later
var comparator = function(a, b, c) {
return typeof funcs[b] === 'function' ? funcs[b](a, c) : null;
};
When comparator is invoked, the returned function looks like:
function() { return this[a] < c; }// Where a, c are pre-determined.
This method can be implemented in this way (demo at JSFiddle):
// Assumed that funcs has been defined
function implementComparator(set, key, operator, value) {
var comparator, newset = [], i;
if (typeof funcs[operator] === 'function') {
comparator = funcs[operator](key, value);
} else { //If the function does not exist...
throw TypeError("Unrecognised operator");
}
// Walk through the whole set
for (i = 0; i < set.length; i++) {
// Invoke the comparator, setting `this` to `set[i]`. If true, push item
if (comparator.call(set[i])) {
newset.push(set[i]);
}
}
return newset;
}
var set = [ {meow: 5}, {meow: 3}, {meow: 4}, {meow: 0}, {meow: 9}]
implementComparator( set , 'meow', '<=', 5);
// equals: [ {meow: 5}, {meow: 3}, {meow: 4}, {meow: 0} ]
For clarification, I constructed this answer, while keeping the following in mind:
The OP requests an simple, easily extensible method with an unknown/dynamic set of operators.
The code is based on the pseudo-code at the OP, without changing anything which could affect the intent of the OP. With some adjustments, this function can also be used for Array.prototype.filter or Array.prototype.sort.
eval (or Function) should not be used at every call to comparator
Don't do it so dynamically....it would be far more efficient to only create the functions once, rather than each time they are called, as that would create a new funciton and so a memory leak each time the compare is done.
var comparator = {
">=": function(a, b) { return a >= b;},
"<=": function(a, b) { return a <= b;},
"add": function(a, b) { return a + b; },
compare: function(typeString, a, b){
if(comparator.hasOwnProperty(typeString) == true){
var theFunction = comparator[typeString];
return theFunction(a, b);
}
else{
alert("typeString '" + typeString + "' not supported.");
}
}, };
var test = comparator.compare(">=", 5, 4);
var comparator = (function () {
var fns = {
'>=': function (a, c) { return a >= c; },
'<=': function (a, c) { return a <= c; },
'<': function (a, c) { return a < c; },
'>': function (a, c) { return a > c; },
'=': function (a, c) { return a == c; },
'==': function (a, c) { return a === c; },
'!=': function (a, c) { return a != c; }
};
return function (b) {
return fns.hasOwnProperty(b) ? fns[b] : null;
};
}());
At this point you can see that nothing is more efficient than an inline expression. It is not clear to me why you think you need to be so dynamic beforehand.

Categories