Why do I get an endless loop using moment.js? - javascript

I got the following string-array:
cohortsDates =
[
'2020-11', '2021-01',
'2021-02', '2021-03',
'2021-04', '2020-10',
'2021-05', '2020-12',
'2021-07'
]
Now I try to sort it that the dates are in an ascending order from 2020-10 to 2021-07 with this code:
cohortsDates.forEach((month) => {
for(var i = 0; i < cohortsDates.length; i++ ) {
if(moment(cohortsDates[i+1]) < moment(cohortsDates[i])) {
var swap = cohortsDates[i]
cohortsDates[i] = cohortsDates[i+1]
cohortsDates[i+1] = swap
}
}
})
console.log(cohortsDates)
But all I get is an endless loop and the sorted array never prints out. Does somebody know, what can I do to fix it?

When i === cohortsDates.length-1 (i.e. you are looking at the last item in your for loop) you test:
if(moment(cohortsDates[i+1]) < moment(cohortsDates[i])) {
Where cohortsDates[i+1] will always be undefined and thus less than the previous value.
So you swap them and assign cohortsDates[i] to cohortsDates[i+1].
This increases cohortsDates.length by 1 so the end condition of the for loop doesn't apply.
You now loop again and cohortsDates[i+1] is still undefined so it goes infinite.
JS has a built-in sort method to do this. Don't reinvent the wheel.

You don't exactly have the most efficient sorting algorithm. When sorting, you should use javascript's array.sort:
cohortsDates.sort((a, b) => moment(a) < moment(b) ? -1 : 1);
The sort method will loop over the array and call the function you passed as an argument with two of the values as parameters. If the function returns a negative number, that means a is less than b, a positive number means a is greater than b.

Related

Sort array by Criticality [duplicate]

This question already has answers here:
javascript sorting array based on another array
(1 answer)
Sort an array in the same order of another array
(4 answers)
Closed 3 years ago.
I have a criticality array and I want to sort by criticality, something like this:
let criticalityTypes = ['CRITICALITY_LOW', 'CRITICALITY_HIGH', 'CRITICALITY_MEDIUM'];
I get this order randomly, sometimes ** CRITICALITY_LOW ** comes in position 1 of the matrix ie either position 2, or 'CRITICALITY_MEDIUM' in 0 position,
What I want to do is order in the following order, regardless of the order that comes to me, sometimes I have just one criticality, or two:
['CRITICALITY_HIGH', 'CRITICALITY_MEDIUM', 'CRITICALITY_LOW'];
I tried to use sort function to order what I've done so far is this:
return criticalityTypes.sort((a, b) => {
if (a < b) return -1;
if (a > b) return 1;
});
But without success, any help?
You could take an object with the wanted order and sort by the delta of the values.
var criticalityTypes = ['CRITICALITY_LOW', 'CRITICALITY_HIGH', 'CRITICALITY_MEDIUM'],
order = { CRITICALITY_HIGH: 1, CRITICALITY_MEDIUM: 2, CRITICALITY_LOW: 3 };
criticalityTypes.sort((a, b) => order[a] - order[b]);
console.log(criticalityTypes);
Just another way:
let criticalityTypes = ['CRITICALITY_LOW', 'CRITICALITY_HIGH', 'CRITICALITY_MEDIUM'];
let orderedItems = [];
let desiredOrder = ['CRITICALITY_HIGH', 'CRITICALITY_MEDIUM', 'CRITICALITY_LOW'];
for (var i = 0; i < desiredOrder.length; i++) {
if (criticalityTypes.indexOf(desiredOrder[i]) > -1) {
orderedItems.push(desiredOrder[i]);
}
}
console.log(orderedItems);
The problem here is that you as the consumer of the api, do not have any clue what an "order" means for this items, as they are not "quantized", they are ideas, not an integer that you could call 'order' so you know what is what, the string is just for the display anyway.
Since you can not change the api, BUT they are always only these 3 and you always want them in the same order, you can mock your current call, put this as a 'placeholder' comparer so that you mock the calls to this function, until someones gives you something that you can use numericaly to determine an order, 'ascending' or 'descending' requires something quantized to have a meaning.
function criticalityTypes(a,b){
return ['CRITICALITY_HIGH', 'CRITICALITY_MEDIUM', 'CRITICALITY_LOW'];
}

Same code runs differently on different devices

I'm new to coding, still learning. My friend gave me a task to write a function that does return the 2nd highest number from an array, I've managed to do it using array.prototype.sort(). He said to replace "-" with a "<" or ">" to make the code more clear, that's where the problem started.
I'm using VCS on windows, and it's not working properly.
My friend uses a mac, everything works fine.
Tried it on jsfiddle, everything works fine.
const secondMax = (arr) => {
return arr.sort((a, b) => b - a)[1]; //does return the correct number after console.log()
};
const secondMax = (arr) => {
return arr.sort((a, b) => a < b)[1]; //does not
};
"a < b" should be sorting descending
"a > b" should be sorting ascending
But no matter which operator I use, the sorting fails and just returns the second number from the array
You're supposed to return a number, not a boolean. So the first is correct. The latter might work by chance on some javascript engines, but it's not guaranteed to.
sort sorts the array as String by default. If you pass a comparator, then it's a function which will depend on two parameters and return:
negative, if the first parameter is smaller than the second
0 if they are equal
positive, if the first parameter is greater than the second
Using a logical operator instead of the above is mistaken.
However, if you are interested in finding the second largest number, then it's better to do it using a cycle:
var largestNumbers = [];
var firstIndex = (arr[0] < arr[1]) ? 1 : 0;
largestNumbers.push(arr[firstIndex]);
largestNumbers.push(arr[1 - firstIndex]);
for (var i = 2; i < arr.length; i++) {
if (largestNumbers[1] < arr[i]) {
if (largestNumbers[0] < arr[i]) {
largestNumbers[1] = largestNumbers[0];
largestNumbers[0] = arr[i];
}
}
}
This is quicker than sorting an array and more importantly, it does not destroy your initial order just to find the second largest number.

Why is the Array.sort() method in my Javascript program unstable? [duplicate]

This question already has answers here:
Fast stable sorting algorithm implementation in javascript
(16 answers)
Closed 6 years ago.
Here is my jsFiddle:
//Change this variable to change the number of players sorted
var numberOfPlayers = 15;
var teams = [];
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for(var a=0; a<numberOfPlayers; a++){
updateStandings();
teams.push(new Team(alphabet.charAt(a)));
}
console.log("Teams:");
for(var x=0; x<teams.length; x++){
console.log(teams[x].name);
}
//Functions and such
function updateStandings(){
teams.sort(function(a, b) {
if(a.score == b.score){
if(a.tiebreak == b.tiebreak){
return teams.indexOf(a)-teams.indexOf(b);
}else{
return b.tiebreak-a.tiebreak;
}
}else{
return b.score-a.score;
}
});
}
function Team(name){
this.name = name;
this.score = 0;
this.tiebreak = 0;
}
I assumed the problem was that javascript sorting was unstable, and changed my compare function, but it still does not work.
The generic approach to stable sorting in JS is as follows:
function stable_sort(array, sortfunc) {
function _sortfunc(a, b) { return sortfunc(array[a], array[b]) || a - b; }
return array.map((e, i) => i) . sort(_sortfunc) . map(i => array[i]);
}
What this actually does is to sort a list of indices. Then it maps the sorted list of indices back to the original array. The sort function is rewritten to compare the values in the array at those indices, and if they are equal then fall back to a comparison of indices themselves.
This approach avoids the problem in your code which is that it is doing indexOf look-ups into an array which is the middle of being sorted.
This question could be informative.
According to the documentation, sort method is not required to be stable: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort In some browsers it is stable, in some not.
You do need to change the compare function, but not in the way that you tried. The reason is that you compare
return teams.indexOf(a)-teams.indexOf(b);
in the current array. It means that if the order of a and b has changed on the previous steps, your sorting routine will preserve this new order, not the one that these elements had in the beginning.
There are different ways to solve it. For example, you can create a copy of the array before sorting and execute indexOf on this copy. It will preserve the order that elements had had before sorting started.
But if your know that order in advance, you can also use this knowledge. For example, if before sorting the teams was sorted by their names, you can compare names as strings instead of positions in the array, it would be much more efficient than the first option.
Because JS' sorting is typically unstable. From ยง22.1.3.24 of the spec:
The elements of this array are sorted. The sort is not necessarily stable (that is, elements that compare equal do not necessarily remain in their original order).
Your teams are created with identical properties except their name, so the line actually performing the sort is:
return teams.indexOf(a)-teams.indexOf(b);
Because you're calling indexOf, it searches for the item (and its index) each repetition of the sort. Sorting mutates the array (from MDN: it "sorts the elements of an array in place and returns the array").
You are searching for the item within the same array you are sorting, so the index may change on each iteration. Done correctly (relatively speaking), you could produce a never-ending sort with that.
For example:
const data = [1, 3, 2, 4];
let reps = 0;
data.sort((a, b) => {
console.log(data);
const ia = data.indexOf(a), ib = data.indexOf(b);
if (ia === ib || reps > 50) {
return 0;
} else if (ia < ib) {
return 1;
} else if (ib < ia) {
return -1;
}
});

JavaScript [ ].sort( ) method sorts when it should leave elements alone

I have an array of objects in JavaScript. I would like to sort them by a particular metric. It so happens in a special case that the metric has the same value every time. Here is an example array:
var myArray = [{"key":400686,"metric":999999},{"key":52601288,"metric":999999},{"key":1380180030,"metric":999999},{"key":909661,"metric":999999},{"key":401336,"metric":999999},{"key":1317275,"metric":999999},{"key":8642696,"metric":999999},{"key":1374360020,"metric":999999},{"key":602871933,"metric":999999},{"key":410174,"metric":999999},{"key":503411,"metric":999999},{"key":401511,"metric":999999},{"key":410196,"metric":999999},{"key":419377,"metric":999999},{"key":429167,"metric":999999},{"key":609656,"metric":999999},{"key":837941,"metric":999999},{"key":2410791,"metric":999999},{"key":4501004,"metric":999999},{"key":8633371,"metric":999999},{"key":1356540155,"metric":999999},{"key":1374360757,"metric":999999}];
When I make something as simple as this call:
myArray.sort( function( a, b ){ return 0; } );
The array gets sorted! It should obviously be left alone since a return value of 0 in the sort function indicates equality.
Has anyone else experienced this issue?
0 indicates equality, which implies that the elements are interchangeable. It doesn't imply that they won't be moved around. It implies that they are free to be swapped or remain where they are and the array will still be sorted.
That happens because the sort is unstable, it doesn't necessarily preserve the relative order of equal items.
You can use a stable sorting algorithm, like merge sort.
If you want identical elements to be sorted into a predictable order, then you need a secondary key that lets you identify which identical element should be before the other. In your example array, you could use a custom sort function like this that would sort first by metric and, if those were identical, then by key:
myArray.sort( function( a, b ){
if (a.metric != b.metric) {
return(b.metric - a.metric);
}
// when metrics are the same, sort by key as secondary sort
return(b.key - a.key)
});
Or, if you want to preserve the existing order for identical elements, then you need a quick pass to add an order value each time before you sort:
// mark each value with it's current position in the array
for (var i = 0; i < myArray.length; i++) {
myArray[i].sortIndexForTies = i;
}
myArray.sort( function( a, b ){
if (a.metric != b.metric) {
return(b.metric - a.metric);
}
// when metrics are the same, sort by the original array position as second sort key
return(b.sortIndexForTies - a.sortIndexForTies)
});
I found a nice workaround where I bias the sorting by adding a delta on the sorted parameter outside the precision I care about on that parameter. The bias makes the array tend to stay in the same order. If there is a single unit of difference in the distance (my precision is in the 1s place) that element gets properly sorted.
for( var i = 0, len = myArray.length, delta = 0; i < len; i++, delta += 0.000001 )
{
myArray[ i ].metric -= delta;
}
function byMetric( a, b )
{
return b.metric - a.metric;
}
myArray.sort( byMetric );

Javascript / JQuery - How do I find the size of this array of objects?

I have this code to iterate through an array of objects:
for (vehicleIndex in scenes[sceneID].vehicles) {
vehicleID = scenes[sceneID].vehicles[vehicleIndex];
...
}
but I need to know how to determine the number of items being iterated through so that on the final item, I can execute a particular function. How do I do this?
Example in ES5:
Object.keys( scenes[sceneID].vehicles ).forEach(function( vehicle, index, arr ) {
if( index === arr.length - 1 ) {
// do something on last entry
}
});
Even tho, "last" just means the last element which was looped over. Since there is specific order within a Javascript object (they are infact unordered). However, we could sort the object key names manually by just chaining a .sort() before the .forEach()
var arraySize = scenes[sceneID].vehicles.length;
var i;
var currentItem;
for (i = 0; i < arraySize; i++) {
currentItem = scenes[sceneID].vehicles[i];
if (i == arraySize - 1) {
// do something special
} else {
// do something not so special ;-)
}
}
scenes[sceneID].vehicles should have a length property.
for (vehicleIndex in scenes[sceneID].vehicles) {
vehicleID = scenes[sceneID].vehicles[vehicleIndex];
...
}
doSomethingWithLastItem(vehicleId);
Because JS does not have block scope, by the time your loop finished vehicleId will be the last item's id.
In generic terms you can get the size of an array by accessing the .length property. You can also get the size of an object using Object.keys(obj).length.
Use this to find the length:
scenes[sceneID].vehicles.length
length is a built-in property in arrays. However, if you want to check for the last item, you have to check
scenes[sceneID].vehicles.length - 1
as arrays are zero-indexed.
Also, you should not use for...in to loop on arrays - if someone extends Array.prototype (or worse, Object.prototype), then you will not be happy. Use a normal for loop (which also allows you to use the final item easily):
var len = scenes[sceneID].vehicles.length;
for (var vehicleIndex = 0; vehicleIndex < len; vehicleIndex++) {
vehicleID = scenes[sceneID].vehicles[vehicleIndex];
//...
}
//Do something with the final item here
//Similar to this: itemFunc(vehicleID);
See this SO question for more details.

Categories