Related
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.
I was doing a little coding challenge, and it was to write a function that would return true if the arr parameter, which was an array of number, was sorted in ascending order. I can't seem to figure this out, but my code, no matter what, always returns true.
function inAscOrder(arr) {
return arr.sort() === arr;
}
As others have pointed out, a major issue with your version is that it uses the mutating sort. Although you could fix this by inserting a slice(0) or some such to clone the array, and then writing an index-by-index equality tester, it's much easier to simply test if each successive entry is larger than the previous one.
Perhaps the most elegant way to do that is through recursion. Here's one example:
const inAscOrder = (a) => a.length < 2 || (a[0] < a[1] && inAscOrder(a.slice(1)))
This might not be workable for larger arrays because of Javascript's recursion depth limits. But an iterative version of the same thing is fairly straightforward:
const inAscOrder = (a) => {
if (a.length < 2) {return true}
for (let i = 1; i < a.length; i++) {
if (a[i -1] > a[i]) {return false}
}
return true
}
You do not need to sort the array to see if it is sorted. Just you need to check whether it is sorted or not.
static boolean arraySortedOrNot(int arr[], int n)
{
// Array has one or no element
if (n == 0 || n == 1)
return true;
for (int i = 1; i < n; i++)
// Unsorted pair found
if (arr[i-1] > arr[i])
return false;
// No unsorted pair found
return true;
}
reference link:
https://www.geeksforgeeks.org/program-check-array-sorted-not-iterative-recursive/
Since array is a mutable datatype, your arr will get modified when you call the sort method on it.
So arr.sort() will modify your array instead of just returning a sorted array. So thats why when u check again if its equal to arr, it will be the same sorted array.
To overcome this I suggest you copy the arr to some another array and sort that and test it.
I am working on this problem from coderbytes:
Using the JavaScript language, have the function SecondGreatLow(arr) take the array of numbers stored in arr and return the second lowest and second greatest numbers, respectively, separated by a space. For example: if arr contains [7, 7, 12, 98, 106] the output should be 12 98. The array will not be empty and will contain at least 2 numbers. It can get tricky if there's just two numbers!
My solution works by removing the greatest and lowest values from the array and then using Math methods to return the second highest and lowest values.
However, when there are two or more instances of the greatest or lowest elements of the array, and their index positions are adjacent to each other, I believe only the first instance of this value is removed and the flow skips over the second instance.
Is there any way to have the loop run through the same index value twice in order to process adjacent greatest or lowest values?
Here are the two iterations of my solution which I've tested.. my original attempt using .forEach and my second using a for loop.. I've console.logged a situation in which the code works and in which it doesn't for each attempt.
I'm really new to all this, almost a month of learning in my free time so explaining yourself as if I'm really dumb is appreciated. Thanks!!!
// * First attempt - using .forEach method *
// outputs the second lowest value in the array
function secondLowest (arr) {
var g = function () {
return Math.min.apply(null, arr);
}
arr.forEach(function (val, indx, arr) {
if (val === g()) {
arr.splice(indx, 1);
}
});
lowestVal = g(); // store this value to be added back in for the secondGreatest function (in case there were only two digits in the arr argument)
return Math.min.apply(null, arr);
}
// number trimmed from the array in the function secondLowest..
// to be added back in for the function secondGreatest
var lowestVal = 0
// adds back the lowest value which was trimmed..
// outputs the second greatest value
function secondGreatest (arr){
arr.splice(0,0,lowestVal);
var g = function () {
return Math.max.apply(null, arr);
}
arr.forEach(function (val, indx, arr) {
if (val === g()) {
arr.splice(indx, 1);
}
});
return Math.max.apply(null, arr);
}
// putting together the output
function SecondGreatLow (arr) {
return secondLowest(arr) + " " + secondGreatest(arr);
}
console.log(SecondGreatLow([1,2,3,4,5]));
console.log(SecondGreatLow([1,1,2,2,3,3,4,4,5,5]));
// * Second attempt - using for loops *
// outputs the second lowest value in the array
function secondLowest (arr) {
var g = function () {
return Math.min.apply(null, arr);
}
lowestVal = g();
for (var i = 0; i < arr.length; i++) {
if (arr[i] === g()) {
arr.splice(i, 1);
}
}
return Math.min.apply(null, arr);
}
// number trimmed from the array in the function secondLowest..
// to be added back in for the function secondGreatest
var lowestVal = 0
// adds back the lowest value which was trimmed..
// outputs the second greatest value
function secondGreatest (arr){
arr.splice(0,0,lowestVal);
var g = function () {
return Math.max.apply(null, arr);
}
for (var i = 0; i < arr.length; i++) {
if (arr[i] === g()) {
arr.splice(i, 1);
}
}
return Math.max.apply(null, arr);
}
// putting together the output
function SecondGreatLow (arr) {
return secondLowest(arr) + " " + secondGreatest(arr);
}
console.log(SecondGreatLow([1,2,3,4,5]));
console.log(SecondGreatLow([1,1,2,2,3,3,4,4,5,5]));
I tried using the delete operator in order to keep the argument array length consistent (rather than shortening it with splice which I think allows the adjacent value to pass into the removed element's index position and not be processed in the next runthrough of the for loop or forEach method) but the Math.min/max.apply methods don't like having 'undefined' in the array argument.
Also if my code is looking ugly/annoying and makes you cringe then please take this opportunity to vent.. helps me learn to write code that doesn't piss people off ;)
** Solution Found **
Thank you for reminding me of the sort method!(function?) Here's what I ended up with:
function SecondGreatLow (arr) {
var secondLow = 0,
secondHigh = 0;
arr.sort(function(a,b){
return a-b;
});
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i-1]) {
secondLow = arr[i];
break;
}
}
for (var j = (arr.length-2); j >= 0; j--) {
if (arr[j] !== arr[j+1]) {
secondHigh = arr[j];
break;
}
}
return secondLow + " " + secondHigh;
}
console.log(SecondGreatLow([1,1,2,2,3,3,4,4,5,5]));
What an awesome community.. I'll be back with more questions and hopefully I'll feel confident enough to even answer some questions in the near future. Thanks!
I feel like perhaps I'm missing something, but the challenge doesn't seem to include a requirement for removing items from the original array, so I don't see why you're modifying it in such a way. The requirements you provided simply state to return 'a b' where a is the second lowest, and b the second highest.
So, I would first recommend sorting the list. Since you know you're working at the upper and lower bounds, you don't have to iterate over anything (nor should you). Your test arrays are already sorted, but ensuring order will make your code more robust and able to handle other inputs. Check out the Arrays API for more details.
While it seems it may be beyond the scope of your problem, you may also want to look into sorting algorithms to learn more about how that all works, rather than relying solely on the API.
Once sorted, you should be able to easily compare inwards from the boundaries to get your second lowest and second highest values.
Also, you shouldn't need to utilize the Math API, simple inequality operators should do the trick (< and >).
EDIT: While I recommend working on the problem yourself, here is a simple solution to the problem. I place it here so if you get stuck you can reference this (and the associated comments) for guidance.
function SecondGreatLow(arr) {
var i;
var j;
var lowest;
var highest;
var secondLowest;
var secondHighest;
//Sort Array
arr.sort(function (a, b) {
return a - b;
});
//Get Bounds
//Since we sorted the array, and the default sort is in
//ascending lexicographical order, then we're guaranteed that
//our 'lowest' value is at index 0 and our 'highest' value is
//at index arr.length -1. Note that these values may be
//equal.
lowest = arr[0];
highest = arr[arr.length - 1];
//Search for second lowest.
for (i = 0; i < arr.length; i++) {
if (arr[i] > lowest) {
secondLowest = arr[i];
break;
}
}
//If we reach the end of the array, but didn't
//find a greater value, then, since the array is sorted,
//we're guaranteed that all values in the array are equal.
//Therefore, the required value comparisons have no meaning,
//and we return 'undefined'.
if (secondLowest === 'undefined') {
return 'undefined';
}
//Search for second highest, working backwards from the
//high end of the array until we reach our crossover point
//with the previous search. Either some value > arr[i] is the
//second highest, or arr[i] is, so there's no point in looking
//at values in the indices lower than i.
for (j = arr.length - 1; j >= i; j--) {
if (arr[j] < highest) {
secondHighest = arr[j];
break;
}
}
return secondLowest + ' ' + secondHighest;
}
var result = SecondGreatLow([3,3,4,5,4,6]);
console.log(result);
JSFiddle
You may create a priority queue limited by 2 elements, then feed it with all the array and pop the value, which would be the answer.
The trivial implementation would look like:
function UniqueNElementSortedQueue(length, comparison) {
this.length = length;
this.data = [];
this.comparison = comparison;
}
UniqueNElementSortedQueue.prototype.push = function(v) {
if (this.data.indexOf(v) > -1) {
return;
}
this.data.push(v);
this.data.sort(this.comparison);
this.data.length = this.length;
};
UniqueNElementSortedQueue.prototype.popIfN = function() {
if (this.data.length == this.length) {
return this.data[this.length - 1];
}
};
JSFiddle: http://jsfiddle.net/fmfv67xy/
The solution is O(N) (one might argue that I have sorting internally and they would be right :-)) by number of operations and O(N) by additional memory (where N is linear to the "next-lowest/greatest" index value)
As the description does not define what to return if it was not sufficient data fed - my implementation returns undefined.
Actually, let me turn my comment into an answer, since I think it always helps to also worry about performance:
Create 4 local variables:
largest and second_largest initialized to a number smaller than anything you'd expect in your array, or to the smallest possible value that your data-type can take on (-2^31 - 1)
smallest and second_smallest initialized to a number larger than anything you'd expect in your array, or the largest possible value for your data-type (2^31)
Loop over your array once:
If you find a number larger than largest, set second_largest to largest and largest to that number
If you find something smaller than largest but larger than second_largest, set second_largest to that number
If you find a number smaller than smallest, set second_smallest to smallest and smallest to that number
If you find something larger than smallest but smaller than second_smallest, set second_smallest to that number
When you're done with your loop, your answer is contained in second_largest and second_smallest
Given how small your arrays seem to be, you might not notice much of a performance difference between this answer and the other suggested ones, but I think it's a good habit to get into to always keep this concern in the back of your head for every line of code you write. In this answer, you process every array element exactly once (i.e. the algorithm runs in O(n)), whereas adding a sorting step leads to every element being processed multiple times in a general case (the best sorting algorithms (Timsort, for example) have an expected runtime of O(n log n)).
One thing to note:
#elclanrs mentioned a special case in his comment ([1, 1]), which according to the definition of the problem does not have a defined solution (multiple 1s all would be considered the largest number, so there is no second-largest). In this case, the algorithm above will still have second_largest and second_smallest set to their initial values.
I have array with decimal nos such as
var idArray = ["98.40", "111.46", "144.47", "180.48", "217.49", "284.50", "424.51", "571.52", "1887.53", "1960.54", "1972.55", "2118.56", "2167.57", "2467.58", "2480.59", "2488.60", "2662.61", "2671.62", "2767.63", "2982.64", "3168.65", "3263.66", "3295.67", "3369.68", "3579.69", "3592.70", "3600.71", "3605.72", "3620.73", "3646.74", "3852.75", "3857.76", "4031.77", "4489.78", "4975.79"]
I found the minimum value in the array as below
var result = Math.min.apply(null, idArray );
I got result as 98.4
Is there a way to return actual value in the array as 98.40
You could code your own:
minInArr = function(arr) {
var smallest = arr[0];
for(var i=1; i<arr.length; i++){
if(parseInt(arr[i],10) < smallest){
smallest = arr[i];
}
}
return smallest
}
Made this code based on this one:
Return index of greatest value in an array
There are a couple of methods in addition to those already here (though one is pretty similar to adeneo's). One is to copy the array, sort it, then get the 0 index value:
var min = idArray.slice().sort(function(a,b){return a - b})[0];
If you don't care about sorting the original array, drop the .slice() part.
Another way is to use Math.min to get the value, then use some to find it in the original array. The benefit of some is that it will stop at the first match:
var min, temp = Math.min.apply(Math, idArray);
idArray.some(function(v){ return temp == v? min = v : false});
console.log(min);
There are pros and cons to each, choose whatever is easiest to maintain.
If it really is an array, you can do it the old fashion way with iteration instead, and return the actual string instead of the parsed number, that way number of decimals is not important.
var idArray = ["98.40", "111.46", "144.47", "180.48", "217.49", "284.50", "424.51", "571.52", "1887.53", "1960.54", "1972.55", "2118.56", "2167.57", "2467.58", "2480.59", "2488.60", "2662.61", "2671.62", "2767.63", "2982.64", "3168.65", "3263.66", "3295.67", "3369.68", "3579.69", "3592.70", "3600.71", "3605.72", "3620.73", "3646.74", "3852.75", "3857.76", "4031.77", "4489.78", "4975.79"];
var result = idArray[0];
idArray.forEach(function(x) {
if (parseFloat(x) < result) result = x; // find smallest number as string instead
});
document.body.innerHTML = result;
or, you could just sort the array and get the first item (I sliced it to not modify the original)
var result = idArray.slice().sort(function(a,b) {
return a - b;
}).shift();
or, use Array.reduce
var result = idArray.reduce(function (a,b) {
return parseFloat(a) < parseFloat(b) ? a : b;
});
Try:
var roundedResult = parseFloat(result).toFixed(2);
The trailing zero has no importance and hence it is truncated. So you have no other go other than storing it as a string.
var result = Math.min.apply(null, idArray);
result = (result+"").test(/\.\d\d$/) ? result : result + "0"
Applying Math.min will always coerce your answer to a number, if you coerce it back to a string you loose any trailing zeros. As others have suggested if you know you will always have a fixed number of digits after the decimal you could use .toFixed.
A better solution that doesn't rely on having a fixed number of decimal points would be to use .reduce:
var result,
idArray = ["98.40", "111.46", "144.47", "180.48", "217.49", "284.50", "424.51", "571.52", "1887.53", "1960.54", "1972.55", "2118.56", "2167.57", "2467.58", "2480.59", "2488.60", "2662.61", "2671.62", "2767.63", "2982.64", "3168.65", "3263.66", "3295.67", "3369.68", "3579.69", "3592.70", "3600.71", "3605.72", "3620.73", "3646.74", "3852.75", "3857.76", "4031.77", "4489.78", "4975.79"];
result = idArray.reduce(function (prev, cur) {
if (+prev < +cur) {
return prev;
} else {
return cur;
}
});
console.log(result); // "98.40"
A quick explanation of what this does:
.reduce iterates over the array and calls the provided function once for each item in the array.
This code just uses the first two parameters available in the function, but there are a couple of others available too. The first parameter is the value returned from the previous call (prev, which will be undefined on the first call). The second parameter will be the value of the current item in the array (cur).
Before comparing the the two they are each coerced from strings to numbers using the Unary plus operator.
If prev is smaller it is returned and the next time the function runs prev will be the same, otherwise cur is returned and become the new value of prev on the next call. It is important to note that when the variables were coerced to compare them that just changed the values being compared in the conditional statement, it did not change the actual value stored in the variable, it remains a string.
After the function has been called on the last item in the array the final value of prev is returned and stored in result.
You could shorten it a little using a ternary statement:
result = idArray.reduce(function (prev, cur) {
return +prev < +cur ? prev : cur;
});
If you aren't afraid to use ES6 syntax (not all browsers currently support it) you could make it even shorter with a arrow function:
result = idArray.reduce((prev, cur) => +prev < +cur ? prev : cur);
The one potential (but unlikely) problem with this approach is that it coerces prev every time it makes a comparison. This adds a tiny bit of overhead to each step in the loop. If performance is a concern it would be better to get away from trying to do it with a one-liner and write a function to do it:
var arrayMin = function (arr) {
var i,
len,
prev, // prev and cur will hold numbers that are coerced from strings
cur, // once when they are first encountered
minIndex; // keep track of the index of the smallest item rather
// than copying a string every time we find a smaller number
prev = +arr[0];
minIndex = 0;
for (i = 1, len = arr.length; i < len; i += 1) {
cur = +arr[i];
if (cur < prev) {
prev = cur;
minIndex = i;
}
}
return arr[minIndex];
};
var result = arrayMin(idArray);
I am having seemingly inconsistent results trying to sort arrays of strings using the sort method, both with and without passing sort() a function:
function sortThese(strArr) {
var words = [];
for (var i = 0; i < strArr.length; i++) {
words.push(String(strArr[i].length + "," + strArr[i]));
}
words = words.sort();
console.log(words);
}
sortThese(["battt","catt","mat"]);
sortThese(["as","assssvvvvt","affggg"]);
in this case my results are:
["3,mat", "4,catt", "5,battt"]
["10,assssvvvvt", "2,as", "6,affggg"]
so it appears that the method is seeing my "10" as "1."
How do I make it see the value as 10?
Futher, if I pass sort this function:
words = words.sort(function(a,b){
return a - b;
});
the result is not sorted:
["4,catt", "5,battt", "3,mat"]
["2,as", "10,assssvvvvt", "6,affggg"]
I would love some clarification regarding how I should expect the sort method to behave, and how to make it sort these strings! Thank you
The "10" comes before "3", because you've made it into a string (ie, "1" comes before "3"). If you want to sort by length, you could use the sort function to compare lengths:
words.sort(function(a, b) { return a.length > b.length; });
Fiddle
You could also create a nested object array, in order to hold a number and the string in each item:
var words = strArr.map(function(str) {
return { len: str.length, str: str }
});
words.sort(function(a, b) { return a.len > b.len; });
(Of course, this second approach is unnecessary for the example case, but may be useful in other cases.)
Fiddle
Sorting strings always uses alphabetical order, not numerical. Just imagine 0=A 1=B 2=C and so on. Letters are not connected as numbers, so everything starting with 1 (respective B) will be sorted before 2 (respective C). That's why you get 10,2,6. For 10, only the 1 counts.
Using a custom sort function is a good way to achieve this. But you cannot subtract strings. Extract the integer before and compare afterwards:
words = words.sort(function(a,b){
//parseInt will read only the first numbers and discard the letters after this
return parseInt(a) - parseInt(b)
});