Javascript array while loop. Comparing results: arr[len] vs arr[len-1] - javascript

Good day everyone!
I've found this great code from this site:
var points = [30,100];
document.getElementById("demo").innerHTML = myArrayMax(points);
function myArrayMax(arr) {
var len = arr.length;
var max = -Infinity;
while (len--) {
if (arr[len] > max) {
max = arr[len];
}
}
return max;
}
source: Find the min/max element of an Array in JavaScript
It really works really well and very fast. I've got a question (out of curiosity). When I tried to add (or manipulate) something, the result didn't match based from the first code. Here is the code:
var points = [30,100];
document.getElementById("demo").innerHTML = myArrayMax(points);
function myArrayMax(arr) {
var len = arr.length;
var max = -Infinity;
while (len--) {
if (arr[len-1] > max) {
max = arr[len-1];
}
}
return max;
}
I've assumed that the array[0] is the first array element, also I used post-decrement function, and yet I didn't get the expected result.

First of all, using arr[len-1] adds a new problem to the mix: Index out of bounds. This happens when the loop iterates the last element in the loop (len == 0 after len--) and you end up looking for arr[-1] which doesn't exist. Also, you're not checking anymore for the last index in the array (arr[arr.length - 1]), since len-- already substracts from the index. Like so:
var points = [30,100];
document.getElementById("demo").innerHTML = myArrayMax(points);
function myArrayMax(arr) {
var len = arr.length; // len == 2
var max = -Infinity;
while (len--) { // len == 1
// checking for arr[len - 1] the first time
// results in checking for arr[0]. arr[1] will be skipped
if (arr[len-1] > max) {
max = arr[len-1];
}
}
return max;
}
The function works well in the first place, so... Why fix what isn't broken?
Check out arithmetic operators for more info on len--.

function myArrayMax(arr) {
var len = arr.length;
var max = -Infinity;
while (len--) { // here itself you are decrementing the value of len
if (arr[len-1] > max) { // here also you are checking the previous one. Then you won't get chance to check the last array element.
max = arr[len-1];
}
}
return max;
}
// moreover you will get index out of bound exception while going in last iteration.

Related

javascript while loop correctly iterating but for loop with same logic is not, on array with integer values and some null values in there

Iterating through a javascript array which has some data in, and some null or not defined values also, is giving funny behaviors with a for loop, but not with a while loop. It is not returning when it should and is stuck in an infinite loop
I have investigated the outputs extensively, the condition whether the number exists in the array is never evaluated to be true, only ever false, but it sometimes enters the if statement region as if it is true. It is seemingly arbitrary.
//function called within this code
function randomArrayOfIndexes() {
var randNumbArray = new Array(4);
var indexToAssign = Math.floor(Math.random() * Math.floor(4));
randNumbArray[0] = indexToAssign;
for (i = 1; i < randNumbArray.length; i++) {
indexToAssign = Math.floor(Math.random() * Math.floor(4));
while (arrayContains(randNumbArray, indexToAssign)) {
indexToAssign = Math.floor(Math.random() * Math.floor(4));
}
randNumbArray[i] = indexToAssign;
}
return randNumbArray;
}
//this works
function arrayContains(arrayin, numberIn) {
var i = arrayin.length;
while (i--) { //takes one from i so highest index is accurate on first iteration
if (arrayin[i] === numberIn) {
return true;
}
}
return false;
}
//this doesn't... not even backwards like the above iteration
function arrayIncludes(arrayin, numberIn) {
for (i = 0; i < arrayin.length; i++) {
if (arrayin[i] === numberIn) {
return true;
}
}
return false;
}
At first each function above is passed in an array with [int value, null, null, null], and a random number; when the function returns, the next null value is filled with the random number that doesn't exist in it already, so [int value, int value, null, null]... until all values are filled... the final array is filled with unique random numbers from 0 to 3, to provide an index for a piece of data in another array... to make sure that it is only used once in the program I am writing.
I would expect it to return true if the number passed in is already in there, another random number then generated outside of the broken function, and the process repeated until a unique random number is found. When it is found, the array being passed back in will be populated at the next available index, and the process repeated. This is not happening. It is getting stuck in an infinite loop, and never returning
you are just missing a var before i:
function arrayIncludes(arrayin, numberIn) {
for (var i = 0; i < arrayin.length; i++) {
// in ^ here
if (arrayin[i] === numberIn) {
return true;
}
}
return false;
}
You may also declare it before loop, like
var i;
for (i = 0; i < arrayin.length; i++) {
...
By the way, this way of generating random numbers without duplicates is very inefficient, I suggest something like having an array of 0-3 (in your current example) or 0-n and then just randomly taking items out of it. then you don't have to loop through the whole array each time you find a new number. every time you just find a random index between 0 and the length of remaining items.
Imagine that the array length is 1000, and the last item remaining is a number like 100, how many times you have to find a random number and loop through whole array till your random number is 100?
var n = 5;
var a = new Array(n);
for(var i=0;i<n;i++) a[i] = i;
var result = new Array(n);
var i = n;
while(i)
{
var index = Math.floor(Math.random() * i);
result[--i] = a[index];
a.splice(index,1);
}
document.getElementById('a').innerHTML = result;
<div id="a"></div>
You need to declare variables in you loops with for i=0. if you don't do this the variable is global and when you use the same loop variable in nested loops one can change the other.
You are using i in both loops so when you call the for loop with:
function arrayIncludes(arrayin, numberIn) {
for (i = 0; i < arrayin.length; i++) {
// etc
}
You set i back to 0 ad iterate it — this is the same i you are using in randomArrayOfIndexes so it interferes with that loop. This is a common cause of hard-to-find bugs and is hy you should always declare loop variables.
Here's the bug in it's simplest form. Notice that the out loop only runs once because i is incremented in the inner loop causing the outloop to exit early:
for (i = 0; i < 4; i++){
console.log("out loop number: ", i)
for (i = 0; i < 4; i++){
console.log("inner_loop: ", i)
}
}
If you declare the variables for for let i =, each loop gets its own version of i both loops run independently:
for (let i = 0; i < 4; i++){
console.log("out loop number: ", i)
for (let i = 0; i < 4; i++){
console.log("inner_loop: ", i)
}
}

multidimensional array indexOf not working js

I'm trying to find an index of a number in a 2d array, but console gives out
Uncaught TypeError: block[((a * 10) + c)].indexOf is not a function
I think it has something to do with the way of accessing the array element, but can't seem to find the problem.
Here's the code.
var block = [];
var temp;
var del;
for(var a = 0;a < 9;a++){
for(var b = 0;b < 9;b++){
temp = parseInt(prompt("enter element number " + b + " of row number " + a));
console.log(temp);
if(temp>0){
block[a*10+b] = temp;
}else{
block[a*10+b] = [1,2,3,4,5,6,7,8,9];
}
// console.log(block[a*10+b]);
}
}
for(var a = 0;a < 9;a++){
for(var b = 0;b < 9;b++){
if(typeof(block[a][b]) == "number"){
for(var c = 0;c < 9;c++){
if(c != b){
del = block[a*10+c].indexOf(b);
block[a*10+c].splice(del,1);
}
}
}
}
}
You have a mix of data types assigned to the block array. When the user enters a value that is not numeric, you assign indeed a nested array to one of the block elements, but not so when the user enters a valid number.
From what I think you are doing (a Sudoko game?) this might be intended: the numbers are known values in the grid, the nested arrays represent a list of values that are still possible at that particular cell.
But then in the second part of your code, you should check in which of the two cases you are, as you only want to remove array elements if the value you are looking at is indeed an array. This test you can do with Array.isArray().
There are also some other issues in the second part of your script:
The expression block[a][b] is not consistent with how you have filled that array: it should be block[a*10+b] to be consistent.
the b in .indexOf(b) is wrong: you are not looking for that value, but for block[a*10+b].
the splice() is always executed, even if the indexOf returned -1. This leads to an undesired effect, because if the first argument to splice() is negative, the index really is counted from the end of the array, and still an element is removed from the array. This should not happen: you should only execute the splice if the indexOf result is non-negative.
Below I have put a working version, but in order to avoid the almost endless prompts, I have provided this snippet with a textarea where you can input the complete 9x9 grid in one go, and then press a button to start the execution of your code:
document.querySelector('button').onclick = function () {
var block = [];
var temp;
var del;
var text = document.querySelector('textarea').value.replace(/\s+/g, '');
for(var a = 0;a < 9;a++){
for(var b = 0;b < 9;b++){
temp = parseInt(text[a*9+b]); // <-- get char from text area
if(temp>0){
block[a*10+b] = temp;
}else{
block[a*10+b] = [1,2,3,4,5,6,7,8,9];
}
}
}
for(var a = 0;a < 9;a++){
for(var b = 0;b < 9;b++){
var num = block[a*10+b]; // <-- get content, fix the index issue
if(typeof num == "number"){
for(var c = 0;c < 9;c++){
if(c != b && Array.isArray(block[a*10+c])){ //<-- add array-test
del = block[a*10+c].indexOf(num); // <-- not b, but num
if (del > -1) // <-- only splice when found
block[a*10+c].splice(del,1);
}
}
}
}
}
document.querySelector('pre').textContent = 'block='+ JSON.stringify(block);
};
<textarea rows=9>
53..7....
6..195...
.98....6.
8...6...3
4..8.3..1
7...2...6
.6....28.
...419..5
....8..79
</textarea>
<button>Process</button>
<pre></pre>
Note that there are elements in block which remain null. I suppose you intended this: as you multiply a with 10, and only store 9 values per "row", there is always one index that remains untouched.
I haven't looked over your second for loop, but you can try applying similar logic there as in the snippet I've provided. The issue is that you need to create a temporary array inside the outer for loop over values of a (but NOT inside the inner, nested for loop over values of b). Inside the for loop for values of b, then, you need to push something into that temporary array (which I called temp). Then, outside of the b for loop, but before the next iteration of a, push that temporary array temp to the block array. In this way, you will generate a 2D array.
var block = [];
var del;
for(var a = 0; a < 9; a++) {
let temp = [];
for(var b = 0; b < 9; b++) {
let num = parseInt(prompt(`Enter element ${b} of row ${a}:`));
if (num > 0) {
temp.push(num);
} else {
// block[a*10+b] = [1,2,3,4,5,6,7,8,9];
temp.push(b);
}
}
block.push(temp);
}

how to check which elements of an array match relative to position

Trying to create a function or two that will be able to check the elements of an array and output wheater the elements of the two arrays are identical (ie same number and identical position is present), or the number is present but does not match the same position as the other array. Basically, I'm attempting to recreate a simple game called mastermind. The main problem im having is a case senarior when say the right answer is [1,2,3,4] and the user will guess [0,1,1,1], my function will out put that the number 1 is present 3 times, and I need to figure out how to just have it say the number 1 is present 1 time. Here is the function that checks the arrays:
function make_move(guess, answ){
var myguess = document.getElementById("mymoves");
var correct_number_correct_spot= 0;
var correct_number_wrong_spot= 0;
for(var i = 0; i < 4; ++i)
{
if(answ[i] == guess[i]){
++correct_number_correct_spot;
}
else if(answ[i] !== guess[i] && $.inArray(guess[i], answ) !== -1){
++correct_number_wrong_spot;
}
}
console.log(answ);
console.log(guess);
myguess.innerHTML += correct_number_correct_spot + " were right!" +correct_number_wrong_spot+ "there but not in the right order";
}
You can keep the count of missed numbers in an object, and subtract the guessed ones that appear in the answer. Then you can calculate the correct_number_wrong_spot subtracting the number of correct_number_correct_spot and the missed ones.
function make_move(guess, answ){
var myguess = document.getElementById("mymoves");
var correct_number_correct_spot = 0;
// Initialize missed counts to the numbers in the answer.
var correct_number_wrong_spot = answ.length;
var missed = {};
for (var j = 0; j < answ.length; j++) {
missed[answ[j]] = (missed[answ[j]] || 0) + 1;
}
for(var i = 0; i < answ.length; ++i)
{
if(answ[i] == guess[i]){
++correct_number_correct_spot;
}
// Subtract the guessed numbers from the missed counts.
if (guess[i] in missed) {
missed[guess[i]] = Math.max(0, missed[guess[i]] - 1);
}
}
// Subtract the correctly spotted numbers.
correct_number_wrong_spot -= correct_number_correct_spot;
// Subtract the remaining missed numbers.
for (var number in missed) {
correct_number_wrong_spot -= missed[number];
}
console.log(answ);
console.log(guess);
myguess.innerHTML += correct_number_correct_spot + " were right!" +correct_number_wrong_spot+ "there but not in the right order";
}
Check demo
EDIT: My try to explain doubts exposed in the comments:
would you mind explining how this code works: for (var j = 0; j < answ.length; j++) { missed[answ[j]] = (missed[answ[j]] || 0) + 1; }
missed[answ[j]] = (missed[answ[j]] || 0) + 1;
This is a quick way to increment the count for a number or initialize it to 0 if it doesn't exists yet. More or less the statement works like this:
If missed[answ[j]] is undefined then it is falsy and hence the || (or operator) evaluates to the 0. Otherwise, if we already have a value greater than 0, then it is truthy and the || evaluates to the contained number.
If it looks weird, you can replace this line with:
if (!(answ[j] in missed)) {
missed[answ[j]] = 0;
}
missed[answ[j]] += 1;
also if (guess[i] in missed) { missed[guess[i]] = Math.max(0, missed[guess[i]] - 1);
missed[guess[i]] = Math.max(0, missed[guess[i]] - 1);
In this case I use Math.max to make sure we don't subtract below 0. We don't want repeated numbers in the guess that exceeds the number of those present in the answer count. I mean, we subtract at most until the number of repeated numbers in the answer.
if (missed[guess[i]] > 0) {
missed[guess[i]] -= 1;
}
Try this fiddle!
Without changing your original function too much, you can use an object as a map to keep track of which numbers you have already matched.
var number_matched = {};
// ...
if(!number_matched[guess[i]]) {
number_matched[guess[i]] = true;
}

Javascript generate random unique number every time

Ok so i need to create four randomly generated numbers between 1-10 and they cannot be the same. so my thought is to add each number to an array but how can I check to see if the number is in the array, and if it is, re-generate the number and if it isnt add the new number to the array?
so basically it will go,
1.create new number and add to array
2.create second new number, check to see if it exist already, if it doesn't exist, add to array. If it does exist, re-create new number, check again etc...
3.same as above and so on.
You want what is called a 'random grab bag'. Consider you have a 'bag' of numbers, each number is only represented once in this bag. You take the numbers out, at random, for as many as you need.
The problem with some of the other solutions presented here is that they randomly generate the number, and check to see if it was already used. This will take longer and longer to complete (theoretically up to an infinite amount of time) because you are waiting for the random() function to return a value you don't already have (and it doesn't have to do that, it could give you 1-9 forever, but never return 10).
There are a lot of ways to implement a grab-bag type solution, each with varying degrees of cost (though, if done correctly, won't ever be infinite).
The most basic solution to your problem would be the following:
var grabBag = [1,2,3,4,5,6,7,8,9,10];
// randomize order of elements with a sort function that randomly returns -1/0/1
grabBag.sort(function(xx,yy){ return Math.floor(Math.random() * 3) - 1; })
function getNextRandom(){
return grabBag.shift();
};
var originalLength = grabBag.length;
for(var i = 0; i < originalLength; i++){
console.log(getNextRandom());
}
This is of course destructive to the original grabBag array. And I'm not sure how 'truly random' that sort is, but for many applications it could be 'good enough'.
An slightly different approach would be to store all the unused elements in an array, randomly select an index, and then remove the element at that index. The cost here is how frequently you are creating/destroying arrays each time you remove an element.
Here are a couple versions using Matt's grabBag technique:
function getRandoms(numPicks) {
var nums = [1,2,3,4,5,6,7,8,9,10];
var selections = [];
// randomly pick one from the array
for (var i = 0; i < numPicks; i++) {
var index = Math.floor(Math.random() * nums.length);
selections.push(nums[index]);
nums.splice(index, 1);
}
return(selections);
}
You can see it work here: http://jsfiddle.net/jfriend00/b3MF3/.
And, here's a version that lets you pass in the range you want to cover:
function getRandoms(numPicks, low, high) {
var len = high - low + 1;
var nums = new Array(len);
var selections = [], i;
// initialize the array
for (i = 0; i < len; i++) {
nums[i] = i + low;
}
// randomly pick one from the array
for (var i = 0; i < numPicks; i++) {
var index = Math.floor(Math.random() * nums.length);
selections.push(nums[index]);
nums.splice(index, 1);
}
return(selections);
}
And a fiddle for that one: http://jsfiddle.net/jfriend00/UXnGB/
Use an array to see if the number has already been generated.
var randomArr = [], trackingArr = [],
targetCount = 4, currentCount = 0,
min = 1, max = 10,
rnd;
while (currentCount < targetCount) {
rnd = Math.floor(Math.random() * (max - min + 1)) + min;
if (!trackingArr[rnd]) {
trackingArr[rnd] = rnd;
randomArr[currentCount] = rnd;
currentCount += 1;
}
}
alert(randomArr); // Will contain four unique, random numbers between 1 and 10.
Working example: http://jsfiddle.net/FishBasketGordo/J4Ly7/
var a = [];
for (var i = 0; i < 5; i++) {
var r = Math.floor(Math.random()*10) + 1;
if(!(r in a))
a.push(r);
else
i--;
}
That'll do it for you. But be careful. If you make the number of random numbers generated greater than the may number (10) you'll hit an infinite loop.
I'm using a recursive function. The test function pick 6 unique value between 1 and 9.
//test(1, 9, 6);
function test(min, max, nbValue){
var result = recursValue(min, max, nbValue, []);
alert(result);
}
function recursValue(min, max, nbValue, result){
var randomNum = Math.random() * (max-min);
randomNum = Math.round(randomNum) + min;
if(!in_array(randomNum, result)){
result.push(randomNum);
nbValue--;
}
if(nbValue>0){
recursValue(min, max, nbValue, result);
}
return result;
}
function in_array(value, my_array){
for(var i=0;i< my_array.length; i++){
if(my_array[i] == value){
console.log(my_array+" val "+value);
return true;
}
}
return false;
}
Here is a recursive function what are you looking for.
"howMany" parameter is count of how many unique numbers you want to generate.
"randomize" parameter is biggest number that function can generate.
for example : rand(4,8) function returns an array that has 4 number in it, and the numbers are between 0 and 7 ( because as you know, Math.random() function generates numbers starting from zero to [given number - 1])
var array = [];
var isMatch= false;
function rand(howMany, randomize){
if( array.length < howMany){
var r = Math.floor( Math.random() * randomize );
for( var i = 0; i < howMany; i++ ){
if( array[i] !== r ){
isMatch= false;
continue;
} else {
isMatch= true;
break;
}
}
if( isMatch == false ){
array.push(r);
ran(howMany, randomize);
}
ran(howMany, randomize);
return array;
}
}
In your answer earlier, you do have a small bug. Instead of
var originalLength = grabBag.length;
for(var i = 0; i < originalLength .length; i++){
console.log(getNextRandom());
}
I believe you meant:
var originalLength = grabBag.length;
for(var i = 0; i < originalLength; i++){
console.log(getNextRandom());
}
Thanks.

Fast grouping of a javascript array

I have an array of a couple thousand strings
['7/21/2011', '7/21/2011', '7/21/2011', '7/20/2011', etc]
I am currently, running this code to group by the string and get the max group value:
var max = 0;
var group = {};
arr.map(function (value) {
if (group[value]) {
group[value]++;
} else {
group[value] = 1;
}
max = Math.max(max, group[value]);
});
Are there any improvements to make this code run faster?
EDIT:
The results are in: http://jsperf.com/javascript-array-grouping2
EDIT EDIT: that test was flawed. Mike Samuel's code was the fastest.
6000 entries test -> http://jsperf.com/javascript-array-grouping2
10K entries test -> http://jsperf.com/javascript-array-grouping
If you're sure this is a hotspot and speed is really important, I would try to cut out several thousand function calls by inlining max and map.
You can also make the body of your function faster by cutting out a comparison.
var max = 0;
var group = {};
for (var i = arr.length; --i >= 0;) {
var value = arr[i];
var n = group[value] = 1 - -(group[value] | 0);
if (n > max) { max = n; }
}
The best thing to do is measure on the browsers you care about.
Yes certainly. I would calculate the max last, instead of every iteration, and not use an if:
var group = {};
arr.map(function (value) {
group[value] = (group[value] || 0) + 1;
});
var max = 0;
for (key in group) {
if (group[key] > max) max = group[key];
}
EDIT: As Mike Samuel says you might get faster by using an index instead of map:
var group = {};
var max = 0;
for (var i = arr.length; --i >= 0;) {
group[value] = (group[value] || 0) + 1;
}
for (key in group) {
if (group[key] > max) max = group[key];
}
I think it really depends on the JS engine that you will run this code on. An alternative I think it's worth a shot is using
n = group[value] = (group[value]||0) + 1;
if (n > max) max = n;
for each element.
I also think that may be using a regular loop can be faster because the variables you will use will be just locals and not closed-over variables of a closure (that are normally slower) and you will also save a function call per element. Both those problems are non-issues if the implementation can inline this closure, but I don't know if there are JS implementations smart enough for that.

Categories

Resources