I was looking at some custom sort function in Javascript & am trying to understand how it really works. Below is the code;
sortArrayBy: function(a, b, param) {
if (a[sortKey] < b[sortKey]) {
return isAscending ? -1 : 1;
}
if (a[sortKey] > b[sortKey]) {
return isAscending ? 1 : -1;
}
return 0;
}
var self = this;
var sortFunc = function(a, b) {
return self.sortArrayBy(a, b, self.get('sortKey')); // What is the last param used for here ?
};
myArr.sort(sortFunc, key); // What is 'key' used for here ?
My question is in 'sortFunc' defintion, what is the last parameter self.get('sortKey') used for & What is 'key' used for in myArr.sort(sortFunc, key)?
Related
I'm attempting to create my own sort function (question Async version of sort function in JavaScript). I've taken merge sort function from Rosetta Code and make it async:
// based on: https://rosettacode.org/wiki/Sorting_algorithms/Merge_sort#JavaScript
async function mergeSort(fn, array) {
if (array.length <= 1) {
return array;
}
const mid = Math.floor(array.length / 2),
left = array.slice(0, mid), right = array.slice(mid);
await mergeSort(fn, left)
await mergeSort(fn, right)
let ia = 0, il = 0, ir = 0;
while (il < left.length && ir < right.length) {
array[ia++] = (await fn(left[il], right[ir]) <= 0) ? left[il++] : right[ir++];
}
while (il < left.length) {
array[ia++] = left[il++];
}
while (ir < right.length) {
array[ia++] = right[ir++];
}
return array;
}
But I'm not sure how I can define default function fn to work the same as in JavaScript.
console.log([1, 2, 3, 10, 11, 100, 20].sort());
What should be the default sorting function to match those in the JavaScript engine?
Should I convert numbers to strings and compare those? What is the proper implementation?
Updated Answer
The default sort method as defined in core.js looks like so
var getSortCompare = function (comparefn) {
return function (x, y) {
if (y === undefined) return -1;
if (x === undefined) return 1;
if (comparefn !== undefined) return +comparefn(x, y) || 0;
return toString(x) > toString(y) ? 1 : -1;
};
Taken from this repo: https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/es.array.sort.js
Based on #jonrsharpe comments I was able to implement proper default function:
function defaultSortFn(a, b) {
if (typeof a !== 'string') {
a = String(a);
}
if (typeof b !== 'string') {
b = String(b);
}
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
that can be used in my sort:
Array.prototype.sort = function(fn = defaultSortFn) {
return mergeSort(fn, this);
};
The ECMAScript specification for Arra.prototype.sort mentions that the comparison (in absence of a comparator) involves:
e. Let xString be ? ToString(x).
f. Let yString be ? ToString(y).
g. Let xSmaller be ! IsLessThan(xString, yString, true).
h. If xSmaller is true, return -1𝔽.
i. Let ySmaller be ! IsLessThan(yString, xString, true).
j. If ySmaller is true, return 1𝔽.
k. Return +0𝔽.
Given that the IsLessThan procedure is also executed when two strings are compared with the < operator, we can faithfully replicate the default callback function as follows:
function (x, y) {
let xString = String(x);
let yString = String(y);
return xString < yString ? -1 : yString < xString ? 1 : 0;
}
How to sort the array of json data having letters and digits?
JS:
function sortOn(property) {
return function (a, b) {
if (a[property] < b[property]) {
return -1;
} else if (a[property] > b[property]) {
return 1;
} else {
return 0;
}
}
}
var data = [{"id":1,name:"text10"},
{"id":4,"name":"text1"}, {"id":4,"name":"text19"}, {"id":4,"name":"text2"}, {"id":4,"name":"text20"},
{"id":5,"name":"book"}];
data.sort(sortOn('name'));
console.log(data);// when I print the JSON getting book,text1,text10..
//But I have to want to show the json as book,text1,text2...
Any one can help me how to sort the name thing having both letters and digits
Please find the jsfiddle for reference.
Very simplistic implementation. Sort on either the number in the name and if it doesn't exist, use the full name. Could be sped up by caching the parseInt || name comparison.
var result = data.sort(function ( a, b ) {
var first = parseInt(/\d+/g.exec(a.name), 10) || a.name,
second = parseInt(/\d+/g.exec(b.name), 10) || b.name;
if (first > second) return 1;
else if (first === second) return 0;
else return -1;
return -1
});
I have following piece of code and this has started breaking after i included Prototype.js into the page.
function JsonArrayByProperty(objArray, prop, direction) {
if (arguments.length < 2) throw new Error("sortJsonArrayByProp requires 2 arguments");
var direct = arguments.length > 2 ? arguments[2] : 1; //Default to ascending
if (objArray && objArray.constructor === Array) {
var propPath = (prop.constructor === Array) ? prop : prop.split(".");
objArray.sort(function (a, b) {
for (var p in propPath) {
if (a[propPath[p]] && b[propPath[p]]) {
a = a[propPath[p]];
b = b[propPath[p]];
}
}
a = a.match(/^\d+$/) ? +a : a;
b = b.match(/^\d+$/) ? +b : b;
return ((a < b) ? -1 * direct : ((a > b) ? 1 * direct : 0));
});
}
}
It breaks at following lines with error
Uncaught TypeError: Object #<Object> has no method 'match'
a = a.match(/^\d+$/) ? +a : a;
b = b.match(/^\d+$/) ? +b : b;
Your problem most likely starts on this line:
for (var p in propPath) {
Once you add prototype.js to your page, you cannot use the common (but incorrect) shortcut of iterating over an array using for(foo in bar). That's because array elements are no longer simple strings or floats, they are full-fledged "extended" objects that happen to evaluate back to strings or floats if you iterate over them correctly.
for(var i = 0; i < propPath.length; i++) {
will get you back on track.
I'm trying to sort an array of values that can be a mixture of numeric or string values (e.g. [10,"20",null,"1","bar","-2",-3,null,5,"foo"]). How can I sort this array such that
null values are always placed last (regardless of sorting order, see jsFiddle)
negative numbers are sorted correctly (i.e. they are less than positive numbers and sort correctly amongst themselves)
? I made a jsFiddle with detailed numeric and string examples (using localeCompare and the numeric option), but will paste the numeric version of my sorting algorithm below as a starting point.
// Sorting order
var order = "asc"; // Try switching between "asc" and "dsc"
// Dummy arrays
var numericArr = [10,20,null,1,-2,-3,null,5];
// Sort arrays
$(".output1").append(numericArr.toString());
numericArr.sort(sortByDataNumeric);
$(".output2").append(numericArr.toString());
// Numeric sorting function
function sortByDataNumeric(a, b, _order) {
// Replace internal parameters if not used
if (_order == null) _order = order;
// If values are null, place them at the end
var dflt = (_order == "asc" ? Number.MAX_VALUE : -Number.MAX_VALUE);
// Numeric values
var aVal = (a == null ? dflt : a);
var bVal = (b == null ? dflt : b);
return _order == "asc" ? (aVal - bVal) : (bVal - aVal);
}
The problem with my string sorting algorithm (see jsFiddle) is that I can't find a way to always place null values last and negative values aren't correctly sorted within themselves (e.g. -3 should be less than -2)
Edit
To answer the comments, I expect [10,"20",null,"1","bar","-2",-3,null,5,"foo"] to sort to [-3,"-2","1",5,10,"20","bar","foo",null,null]
You should first check to see if either value is null and return the opposite value.
On a side note:
For your default _order value, you should check if the parameter is undefined instead of comparing its value to null. If you try to compare something that is undefined directly you will get a reference error:
(undefinedVar == null) // ReferenceError: undefinedVar is not defined
Instead, you should check if the variable is undefined:
(typeof undefinedVar == "undefined") // true
Also, it's probably a better idea to wrap your compare function in a closure instead of relying on a global order variable.
Sometime like:
[].sort(function(a, b){ return sort(a, b, order)})
This way you can sort at a per-instance level.
http://jsfiddle.net/gxFGN/10/
JavaScript
function sort(a, b, asc) {
var result;
/* Default ascending order */
if (typeof asc == "undefined") asc = true;
if (a === null) return 1;
if (b === null) return -1;
if (a === null && b === null) return 0;
result = a - b;
if (isNaN(result)) {
return (asc) ? a.toString().localeCompare(b) : b.toString().localeCompare(a);
}
else {
return (asc) ? result : -result;
}
}
function sortByDataString(a, b) {
if (a === null) {
return 1;
}
if (b === null) {
return -1;
}
if (isNumber(a) && isNumber(b)) {
if (parseInt(a,10) === parseInt(b,10)) {
return 0;
}
return parseInt(a,10) > parseInt(b,10) ? 1 : -1;
}
if (isNumber(a)) {
return -1;
}
if (isNumber(b)) {
return 1;
}
if (a === b) {
return 0;
}
return a > b ? 1 : -1;
}
fiddle here: http://jsfiddle.net/gxFGN/6/
I left out the order parameter, but you could always reverse the array at the end if needed.
Use this:
function typeOrder(x) {
if (x == null)
return 2;
if (isNaN(+x))
return 1;
return 0;
}
function sortNumber(a, b) {
a = parseInt(a, 10); b = parseInt(b, 10);
if (isNaN(a) || isNaN(b))
return 0;
return a - b;
}
function sortString(a, b) {
if (typeof a != "string" || typeof b != "string")
return 0;
return +(a > b) || -(b > a);
}
order = order == "dsc" ? -1 : 1;
numericArr.sort(function(a, b) {
return order * ( typeOrder(a)-typeOrder(b)
|| sortNumber(a, b)
|| sortString(a, b)
);
});
(updated fiddle)
I'm pretty sure that your problem is a red herring... the abstract function that you past into sort doesn't get a third parameter (in your case _order). So in your situation that's always going to be undefined.
Please reconsider your code with that in mind and see what you get.
The array you specify is entirely Numeric so your sort should work correctly, though as other commenters have suggested, if your array ever winds up with string values (i.e. "10", "-7" etc) you'll want to parseInt and test for isNaN before doing your comparison.
Alright, I or someone I work with broke the syntax here somewhere, and I'm not sure where, as the debugger is giving me some random garble as the error. Anyway here is the function, I think I'm missing a bracket somewhere, but this is just evading me for some reason.
var sort_by = function(field, reverse, primer) {
var key = function (x) {return primer ? primer(x[field]) : x[field]};
return function (a,b) {
var A = key(a), B = key(b);
return ((A < B) ? -1 : (A > B) ? +1 : 0)) * [-1,1][+!!reverse];
}
}
there's an extra closing parenthesis on the line
return ((A < B) ? -1 : (A > B) ? +1 : 0))
should be
return ((A < B) ? -1 : (A > B) ? +1 : 0) ...etc
It would be useful if could provide the debugger error anyway. I exectued it in Chrome Developer Console and it gave the error:
SyntaxError: Unexpected token )
Which made it easy to find this broken line:
return ((A < B) ? -1 : (A > B) ? +1 : 0)) * [-1,1][+!!reverse];
You have unbalanced parenthesis. It should be:
return ((A < B) ? -1 : (A > B) ? +1 : 0) * [-1,1][+!!reverse];
There's one extra closing bracket here. Remove it.
return ((A < B) ? -1 : (A > B) ? +1 : 0)) * [-1,1][+!!reverse];
Also, semicolon everything.
var sort_by = function(field, reverse, primer) {
var key = function(x) {
return primer ? primer(x[field]) : x[field];
};
return function(a, b) {
var A = key(a), B = key(b);
return ((A < B) ? -1 : (A > B) ? +1 : 0) * [-1, 1][+!!reverse];
};
};