I am sorting an array of countries, and each have a data-weight attribute which I use in my custom sort function.
At this point, a_weight and b_weight are either 0 or 1 (integer). 'United States of America' is the only element with a data-weight of 1, every other element has a data-weight of 0.
...
return results.sort(sortOnWeight);
}
function sortOnWeight(a,b) {
a_weight = parseInt($(a['element'][0]['attributes']['data-weight']).val(), 10);
b_weight = parseInt($(b['element'][0]['attributes']['data-weight']).val(), 10);
if (a_weight > b_weight){
return -1;
} else if (a_weight < b_weight) {
return 1;
} else {
return 0;
}
}
The array comes back correct EXCEPT for the last item that hits the 'sortOnWeight' function, which is returned out of order.
For example...result returns as:
United States of America
Mozambique
Aruba
Australia
Austria
Belarus
Anybody see a reason why this could be happening?
It looks like you're banking on the browser sorting algorithm being stable. This is not always the case (see this issue for V8 as an example).
EDIT: More information about V8's sorting here. Apparently, arrays with length <= 10 use a stable sort, while length >= 11 uses a faster unstable sort.
You should sort by text if the weights are equal. String comparison can be done with the > and < operators (MDN reference). The comparison is case-sensitive, with uppercase sorting above lowercase ("abc" > "Abc"), so you may need to use toLowerCase/toUpperCase to fudge your results.
function sortOnWeight(a,b) {
var a_weight = parseInt($(a['element'][0]['attributes']['data-weight']).val(), 10);
var b_weight = parseInt($(b['element'][0]['attributes']['data-weight']).val(), 10);
var a_text;
var b_text;
if (a_weight > b_weight){
return -1;
}
else if (a_weight < b_weight) {
return 1;
}
else {
a_text = /* get text from a */
b_text = /* get text from b */
return a_text > b_text;
}
}
From Mozilla documentation sort is considered unstable:
The sort is not necessarily stable. The default sort order is lexicographic (not numeric).
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
This is also mentioned in ES documentation:
The sort is not necessarily stable (that is, elements that compare equal do not necessarily remain in their original order).
http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.11
I just wanted to try it for myself, so created a new 2D array with country names and weights and tried to sort based on weight. It worked to my satisfaction, with US showing up at the top, see the code below which might help you. Of course, this is oversimplified and the links provided by others have a lot more good information and may be necessary depending upon your situation.
<!DOCTYPE html> <html> <body>
<script type = "text/javascript"> var countries = [["Aruba", 0],["Australia", 0],["Austria", 0],["Belarus", 0],["United States of America", 1], ["Mozambique", 0]];
var list = "unsorted :"; for (var i=0;i<countries.length;i++) { list +=countries[i]; list+= " "; }
alert('unsorted array:' + countries);
countries =countries.sort(function(a,b){ return (a[1] > b[1] ? -1 : (a[1] < b[1] ? 1 : 0)); });
list = "sorted :"; for (var i=0;i<countries.length;i++) { list +=countries[i]; list+= " "; }
alert('sorted array:' + countries);
</script>
</body> </html>
Related
I'm trying to understand how .sort works. And so far, the compare function and returns make zero sense.
Here's an array of names in the form Last Name, First Name.
const people = ['Beck, Glenn', 'Becker, Carl', 'Beckett, Samuel', 'Beddoes, Mick', 'Beecher, Henry', 'Beethoven, Ludwig', 'Begin, Menachem', 'Belloc, Hilaire', 'Bellow, Saul', 'Benchley, Robert', 'Benenson, Peter', 'Ben-Gurion, David', 'Benjamin, Walter', 'Benn, Tony', 'Bennington, Chester', 'Benson, Leana', 'Bent, Silas', 'Bentsen, Lloyd', 'Berger, Ric', 'Bergman, Ingmar', 'Berio, Luciano', 'Berle, Milton', 'Berlin, Irving', 'Berne, Eric', 'Bernhard, Sandra', 'Berra, Yogi', 'Berry, Halle', 'Berry, Wendell', 'Bethea, Erin', 'Bevan, Aneurin', 'Bevel, Ken', 'Biden, Joseph', 'Bierce, Ambrose', 'Biko, Steve', 'Billings, Josh', 'Biondo, Frank', 'Birrell, Augustine', 'Black, Elk', 'Blair, Robert', 'Blair, Tony', 'Blake, William'];
I want to arrange them by last name, low to high.
Here's my code (I'm following this tutorial):
const alpha = people.sort((lastOne, nextOne) => {
const [aFirst, aLast] = lastOne.split(', ');
const [bFirst, bLast] = lastOne.split(', ');
return aLast < bLast ? 1 : -1;
});
This obviously works because it's from the tutorial. But I don't understand why it works.
First, these two lines make zero difference in the output - why is that:
return aLast < bLast ? 1 : -1;
return aLast > bLast ? 1 : -1;
Second, from the MDN pages, it says returning a value less than zero will put the first value before the second value. Returning a value greater than zero will put the second value before the first value.
With those two things, I wrote it this way:
return aLast < bLast ? -1 : 1: If a is smaller than b, put a first, b second (which would be ascending right?). Is this not correct?
But in the tutorial, it's written this way:
return aLast < bLast ? 1 : -1;: Which I read as - If a is smaller than b, return 1, which would put b before a (which would be descending right). But this is not what happens in the result. It still arranges from low to high (Or Beck to Blake).
You are using lastOne for both values (instead of nextOne). So
const [aFirst, aLast] = lastOne.split(', ');
const [bFirst, bLast] = lastOne.split(', ');
should probably be
const [aFirst, aLast] = lastOne.split(', ');
const [bFirst, bLast] = nextOne.split(', ');
I'm manipulating a lot of numbers in my application. For this particular case, here is what I do : I retrieve two lists of numbers, I do an average for each of these list, then I substract the two average. To avoid average like 3.333333333333 I use .toFixed(3) on my results.
Here is what it looks like :
// I found this function somewhere on Stackoverflow
Array.prototype.average = function() {
if(this.length == 0){
return 0;
}
else{
return this.reduce(function (p, c) {
return p + c;
}) / this.length;
}
};
sumHigh = [ 10.965, 10.889, 10.659, 10.69, 10.599 ]
sumLow = [ 4.807, 3.065, 2.668, 2.906, 3.606, 4.074, 4.153 ]
// Ok normal
console.log(sumHigh.average()) // 10.760399999999999
console.log(sumLow.average()) // 3.6112857142857138
// Ok normal
console.log(sumHigh.average().toFixed(3)) // "10.760" Does the ".." has anything to do with my problem ?
console.log(sumLow.average().toFixed(3)) // "3.611"
// So here I have my two average values with no more than 3 numbers after the comma but it is not taken into account when substracting these two numbers...
// Not Ok, why 10.760 - 3.611 = 7.148999999999999 ?
console.log(sumHigh.average().toFixed(3) - sumLow.average().toFixed(3)) // 7.148999999999999
console.log(parseFloat(sumHigh.average().toFixed(3)) - parseFloat(sumLow.average().toFixed(3))) // 7.148999999999999
// Just as an example, this is working
console.log(parseFloat(sumHigh.average().toFixed(3)) + parseFloat(sumLow.average().toFixed(3))) // 14.371
console.log(parseFloat(sumHigh.average()) + parseFloat(sumLow.average())) // 14.371685714285713
Can someone explain this behaviour?
Why substraction is not working while addition is?
Ok I know I can solve my problem with :
console.log((sumHigh.average() - sumLow.average()).toFixed(3)) // "7.149"
But that doesn't explain this behaviour.
Thanks
Is there a library or cross-browser native implementation to sort an array of Danish strings alphabetically in JavaScript?
[Aalborg, Sorø ...]
Unfortunately #Tibos solution doesnt work. The danish letters æøå is not sortable as a dane would expect. And it certainly wont work with aa, which is considered as an oldschool å. The only solution is to make an "handheld" sort-algorithm.
Here is working solution :
arr.sort(function(a,b) {
function getCode(c) {
c=c.toLowerCase();
if (c.substring(0,2)=='aa') return 300;
switch (c.charCodeAt(0)) {
case 229 : //å
return 299;
break;
case 248 : //ø
return 298;
break;
case 230 : //æ
return 297;
break;
default :
return c.charCodeAt(0);
break;
}
}
return getCode(a) - getCode(b);
});
The test array
var arr = ['Ølby', 'Ålestrup', 'Ærø', 'Almindingen', 'Aalborg', 'Sorø'];
is by locale sorted as
["Ølby", "Ærø", "Ålestrup", "Sorø", "Almindingen", "Aalborg"]
Which is totally wrong. The above function sort the array correct :
["Almindingen", "Sorø", "Ærø", "Ølby", "Ålestrup", "Aalborg"]
Update
#tibos was absolutely right. The above algorithm just sort by the first letter. The below function converts the strings to array of integers, according to the sorting-scheme from the algorithm above. Then it compares the integer arrays - by that, the strings are sorted in their full length :
arr.sort(function(a,b) {
var d, e, f;
function getIntArray(c) {
var array=[];
c=c.toLowerCase();
for (var i=0;i<c.length;i++) {
if (c.substring(i,2)=='aa') {
array.push(300);
i++;
} else {
switch (c.charCodeAt(i)) {
case 229 : //å
array.push(299);
break;
case 248 : //ø
array.push(298);
break;
case 230 : //æ
array.push(297);
break;
default :
array.push(c.charCodeAt(i));
break;
}
}
}
return array;
}
d=getIntArray(a);
e=getIntArray(b);
for (f=0;f<d.length;f++) {
if (d[f]!=e[f]) {
return d[f] - e[f];
}
}
});
test array :
var arr = ['Ølby', 'Ålborg', 'Århus', 'Ålestrup', 'Åkikrkeby', 'Ærø', 'Almindingen', 'Aalborg', 'Sorø'];
is now sorted in full length :
["Almindingen", "Sorø", "Ærø", "Ølby", "Åkikrkeby", "Ålborg", "Ålestrup", "Århus", "Aalborg"]
You can use the following method to sort strings in any language in Chrome and IE11:
var arr = ['Aalborg', 'Sorø']; // array to sort
var myLocale = 'da-DK'; // danish locale
var sortedArr = arr.sort(function(a,b) { return a.localeCompare(b, myLocale); }); // sort
console.log(sortedArr);
For a more browser agnostinc solution, you have two options:
shim the localeCompare function (or replace it with one only for danish)
change the whole sorting algorithm
For this task i would use a bucket-sort-like algorithm that should in theory run faster than the default sort (it would make the minimal number of comparisons).
The general idea is that you go through each string, place them in (sorted) buckets according to the first letter. You continue to split each bucket containing at least 2 strings according to the second letter, then the third and so on. At the end you merge the buckets and you have the sorted array.
I'd like to come up with a good way to have a "suggested" order for how to sort an array in javascript.
So say my first array looks something like this:
['bob','david','steve','darrel','jim']
Now all I care about, is that the sorted results starts out in this order:
['jim','steve','david']
After that, I Want the remaining values to be presented in their original order.
So I would expect the result to be:
['jim','steve','david','bob','darrel']
I have an API that I am communicating with, and I want to present the results important to me in the list at the top. After that, I'd prefer they are just returned in their original order.
If this can be easily accomplished with a javascript framework like jQuery, I'd like to hear about that too. Thanks!
Edit for clarity:
I'd like to assume that the values provided in the array that I want to sort are not guaranteed.
So in the original example, if the provided was:
['bob','steve','darrel','jim']
And I wanted to sort it by:
['jim','steve','david']
Since 'david' isn't in the provided array, I'd like the result to exclude it.
Edit2 for more clarity:
A practical example of what I'm trying to accomplish:
The API will return something looking like:
['Load Average','Memory Usage','Disk Space']
I'd like to present the user with the most important results first, but each of these fields may not always be returned. So I'd like the most important (as determined by the user in some other code), to be displayed first if they are available.
Something like this should work:
var presetOrder = ['jim','steve','david']; // needn't be hardcoded
function sortSpecial(arr) {
var result = [],
i, j;
for (i = 0; i < presetOrder.length; i++)
while (-1 != (j = $.inArray(presetOrder[i], arr)))
result.push(arr.splice(j, 1)[0]);
return result.concat(arr);
}
var sorted = sortSpecial( ['bob','david','steve','darrel','jim'] );
I've allowed for the "special" values appearing more than once in the array being processed, and assumed that duplicates should be kept as long as they're shuffled up to the front in the order defined in presetOrder.
Note: I've used jQuery's $.inArray() rather than Array.indexOf() only because that latter isn't supported by IE until IE9 and you've tagged your question with "jQuery". You could of course use .indexOf() if you don't care about old IE, or if you use a shim.
var important_results = {
// object keys are the important results, values is their order
jim: 1,
steve: 2,
david: 3
};
// results is the orig array from the api
results.sort(function(a,b) {
// If compareFunction(a, b) is less than 0, sort a to a lower index than b.
// See https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort
var important_a = important_results[a],
important_b = important_results[b],
ret;
if (important_a && !important_b) {ret = -1}
else if (important_b && !important_a) {ret = 1}
else if (important_a && important_b) {ret = important_a - important_b}
else {ret = 0}; // keep original order if neither a or b is important
return(ret);
}
)
Use a sorting function that treats the previously known important results specially--sorts them to the head of the results if present in results.
items in important_results don't have to be in the results
Here's a simple test page:
<html>
<head>
<script language="javascript">
function test()
{
var items = ['bob', 'david', 'steve', 'darrel', 'jim'];
items.sort(function(a,b)
{
var map = {'jim':-3,'steve':-2,'david':-1};
return map[a] - map[b];
});
alert(items.join(','));
}
</script>
</head>
<body>
<button onclick="javascript:test()">Click Me</button>
</body>
</html>
It works in most browsers because javascript typically uses what is called a stable sort algorithm, the defining feature of which is that it preserves the original order of equivalent items. However, I know there have been exceptions. You guarantee stability by using the array index of each remaining item as it's a1/b1 value.
http://tinysort.sjeiti.com/
I think this might help. The $('#yrDiv').tsort({place:'start'}); will add your important list in the start.
You can also sort using this function the way you like.
Live demo ( jsfiddle seems to be down)
http://jsbin.com/eteniz/edit#javascript,html
var priorities=['jim','steve','david'];
var liveData=['bob','david','steve','darrel','jim'];
var output=[],temp=[];
for ( i=0; i<liveData.length; i++){
if( $.inArray( liveData[i], priorities) ==-1){
output.push( liveData[i]);
}else{
temp.push( liveData[i]);
}
}
var temp2=$.grep( priorities, function(name,i){
return $.inArray( name, temp) >-1;
});
output=$.merge( temp2, output);
there can be another way of sorting on order base, also values can be objects to work with
const inputs = ["bob", "david", "steve", "darrel", "jim"].map((val) => ({
val,
}));
const order = ["jim", "steve", "david"];
const vMap = new Map(inputs.map((v) => [v.val, v]));
const sorted = [];
order.forEach((o) => {
if (vMap.has(o)) {
sorted.push(vMap.get(o));
vMap.delete(o);
}
});
const result = sorted.concat(Array.from(vMap.values()));
const plainResult = result.map(({ val }) => val);
Have you considered using Underscore.js? It contains several utilities for manipulating lists like this.
In your case, you could:
Filter the results you want using filter() and store them in a collection.
var priorities = _.filter(['bob','david','steve','darrel','jim'],
function(pName){
if (pName == 'jim' || pName == 'steve' || pName == 'david') return true;
});
Get a copy of the other results using without()
var leftovers = _.without(['bob','david','steve','darrel','jim'], 'jim', 'steve', 'david');
Union the arrays from the previous steps using union()
var finalList = _.union(priorities, leftovers);
Good day stack people,
I'm doing research for my self on how to combine two Json feeds in one and display them in one timeline by date using JS or jQuery.
For example we will have two json files file1.json and file2.json (one from twitter and another from filckr).
I need "n" numbers of latest items and show append them to show by items time.
Any ideas or hints?
Thank you!
P.S. Example feeds: http://twitter.com/status/user_timeline/ignaty.json?count=5 and http://api.flickr.com/services/feeds/groups_pool.gne?%20id=675729#N22&lang=en-us&format=json
Lets pick only any one value from each.
Here's some method that should do that (you need to tweak it though).
Essentially you request the two APIs and then (once both requests are completed) you sort an array of normalized objects.
var all = [];
var waiting = 2; // number of services you request
// once you get response1 or response2
function parseFlickr(data) {
$.each(data, function(index, item) {
// normalize item here depending on service format (parse date)
var normalized = {};
normalized.date = new Date(Date.parse(item.date));
all.push(normalized);
});
if(--waiting == 0) { onDone(); }
}
function onDone() {
all.sort(function(a,b) {
// switch -1 and +1 to invert ordering
return (a.date < b.date ? -1 : (a.date > b.date ? +1 : 0));
});
// do the rendering/appending (you might limit the amount here)
}