Finding entry in 'different places' when navigating an implicit binary search tree - javascript

So I created an implicit binary tree, and a simple way of navigating through that tree that reacts when, for example, you push the left arrow, right arrow, or up arrow:
var items = [ .... ];
function newItem() {
var me = this;
this.item_location = 1;
this.leftItem = function() {
var left = items[(me.item_location * 2)];
if(left){
me.theItem = //create element, insert item from 'left' var, display item
me.item_location *= 2;
} else {}
}
this.rightItem = function() {
var right = items[(me.item_location * 2) + 1];
if(right){
me.theItem = //create element, insert item from 'right' var, display item
me.item_location = (me.item_location * 2) + 1;
} else {}
this.upItem = function() {
var up = items[Math.floor(me.item_location / 2)];
if(up){
me.theItem = //as above
me.item_location = Math.floor(me.item_location / 2);
} else {
// abandon setting this item, return to a different context/menu
}
}
My issue is that I'm finding 'duplicates' that aren't actually there, so there must be a bug in either this tree navigation code or in the code I'm using to check for duplicates:
function findItem(theItem) {
var itemLocations = [];
for(var i = items.length - 1; i >= 0; i--) {
if(items[i] === theItem) {
itemLocations.push(i);
}
}
for(var i = itemLocations.length - 1; i >= 0; i--) {
console.log(itemLocations[i]);
}
console.log(itemLocations);
return itemLocations;
}
What is wrong with this? It's consistently ending up with certain entries showing up twice, but the findItem function consistently finds only one entry.
There's nothing more to the code that could possibly cause this, so I feel like I've missed something super obvious.

Related

In javascript, after i pass a non null object to a funtion it says the object is null

i was trying to implement the A* algorithm and followed the wikipedia pseudo code to make this.
when i pass a predefined object pixel to the a funtion getG() it says that the object is null. I'm sorry if i am not pointing to a specific problem but i am not even sure how to really specify the problem by name. i have tried commenting out the code to increase readability.
git repository link of the whole project - https://github.com/NirobNabil/WhirlWind
(things are a little messy here because i didn't use github at first and i uploaded it just a little ago for posting the problem)
[ i'm actually making this to use a* to find path for my bot which is powered by arduino. thats why i'm using involt. ]
here goes the code,
$(function() {
// define the height, width and bot size in centemeter
total_width = 200;
total_height = 200;
bot_size = 20;
total_box = (total_height / bot_size) * (total_width / bot_size);
box_in_x = total_width / bot_size;
box_in_y = total_height / bot_size;
//populating the pixels array
populate(total_width / bot_size, total_height / bot_size, "UND");
pathfind(pixels, pixels[13], pixels[pixels.length - 1]);
})
var pixels = []; //an array to hold all the objects(the grid)
var obstacles = []; //array to hold the obstacles
function pixel(X, Y, obs) {
this.X_co_ordinate = X;
this.Y_co_ordinate = Y;
this.state = obs; //availale states OPN, UND, OBS, DIS, NULL
this.g = 0;
this.h = 0;
this.f = 0;
this.last = null;
} //every block in the grid is a pixel
//01719372596
function populate(height, width, obs_val = "UND") {
pixels[0] = new pixel(0, 10, obs_val);
for (h = height, i = 0; h >= 0; h--) {
for (w = 0; w < width; w++, i++) {
var temp_obs = new pixel(w, h, obs_val);
temp_obs.last = pixels[0];
pixels[i] = temp_obs; //saving temp_pixel object to pixels array
}
}
} //populating the grid AKA pixels with pixel objects or blocks
// this funtion is where the problem shows up
function getG(current, start) {
let g = 1;
while (current != start && current.last != start && current) {
current = current.last;
g++;
}
return g;
} //get the g val(cost to come to this pixel from the start) of the current pixel
function getH(current, end) {
let I = Math.abs(current.X_co_ordinate - end.X_co_ordinate) + Math.abs(current.Y_co_ordinate - end.Y_co_ordinate);
return I;
} //get the h val(heuristic) of the current pixel
function getF(start, current, end) {
let G = getG(current, start);
let H = getH(current, end);
return G + H;
} //get the f val(total) of the current pixel
function lowFinArray(arr, start, end) {
// here arr is the grid/pixel
let current_low = arr[0];
for (let i = 0; i < arr.length; i++) {
let getF1 = getF(start, current_low, end);
let getF2 = getF(start, arr[i], end);
if (getF1 < getF2) {
current_low = arr[i];
}
}
console.log("current low");
console.log(current_low);
return current_low;
}
function getneighbours(grid, current) {
let neightbours = [];
neightbours.push(grid[getIndex(current.X_co_ordinate - 1, current.Y_co_ordinate)]);
neightbours.push(grid[getIndex(current.X_co_ordinate + 1, current.Y_co_ordinate)]);
neightbours.push(grid[getIndex(current.X_co_ordinate, current.Y_co_ordinate - 1)]);
neightbours.push(grid[getIndex(current.X_co_ordinate, current.Y_co_ordinate + 1)]);
/*
for(i=0; i<neightbours.length; i++){
neightbours[i].last = current;
}*/
console.log("neightbours");
console.log(neightbours);
return neightbours;
} //get the neighbour pixels of the current pixel
//main algo
function pathfind(grid, start, end) {
let closedSet = [];
let openSet = [];
openSet.push(start);
let current = start;
//trying to debug
console.log("low F in arr");
console.log(lowFinArray(grid, start, end));
console.log(start);
console.log(current);
console.log(end);
console.log(grid);
let x = 0;
while (openSet.length > 0) {
//trying to debug
console.log("executed " + (x++));
console.log("openset");
console.log(openSet);
current = lowFinArray(grid, start, end); //assigning the pixel with lowest f val to current
console.log("current");
console.log(current);
if (current == end) {
console.log(getPath(current));
}
let neighbours = getneighbours(grid, current);
for (let i = 0; i < neighbours.length; i++) {
let neighbour = neighbours[i];
if (closedSet.includes(neighbour)) {
continue;
}
if (!openSet.includes(neighbours)) {
openSet.push(neighbours);
}
//console.log(current);
let getg = getG(current, start);
let geth = getH(current, end);
//console.log(getg);
let tGscore = getg + geth; //here getH is being used as a distance funtion
if (tGscore >= getg) {
continue;
}
neighbour.last = current;
neighbour.g = tGscore;
neighbour.f = getF(neighbour);
}
if (x > 10) {
return 0;
}; //the loop was running forever so i tried this to stop the loop after 10 iterations
}
}
function getPath(current) {
let path = [current];
while (current.last != null) {
path.push(current.last);
}
return path;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
and here is what the console says,
Uncaught TypeError: Cannot read property 'last' of null
at getG (app.js:226)
at getF (app.js:241)
at lowFinArray (app.js:249)
at pathfind (app.js:292)
at HTMLDocument.<anonymous> (app.js:92)
at mightThrow (jquery-3.1.1.js:3570)
at process (jquery-3.1.1.js:3638)
You're doing your checks in the wrong order:
while (current != start && current.last != start && current) {
There's no point in using && current after you've already used current.last.
Perhaps changing the order would solve the problem. It will at least get rid of your current error:
while (current && current != start && current.last != start) {
Regarding the title of this question:
In javascript, after i pass a non null object to a funtion it says the object is null
It may very well be non-null 100% of the time you pass it into the function, but you are repeatedly overwriting its parameter within the function, so all bets are off.

avoid same value to appear again using math.random()

animations = ['fadeIn','fadeInDown','slideInUp','flipInY','bounceInLeft'];
Imagine I generate random effect whenever user click something, so to achieve best experience, I would want the user to have same effect. But with
animations[ Math.floor(Math.random() * animations.length) -1];
that would happens.
How to avoid same value to appear again?
Two ways that i can suggest.
First shuffle the array and go one by one from index 0 to 5 and then loop as much as you like.
Pick a random element and slice it out up until the array is empty and then refresh your array from a back up. (be careful not to back up with a reference or your backup array gets deleted along with the one gets spliced. so use .slice())
Array.prototype.shuffle = function(){
var a = this.slice(), // don't morph the original
i = a.length,
j;
while (i > 1) {
j = ~~(Math.random()*i--);
a[i] = [a[j],a[j]=a[i]][0];
}
return a;
};
var album = ["photo1","photo2","photo3","photo4","photo5"];
photos = album.shuffle();
photos.forEach(p => console.log(p));
console.log("another way") // the splice way
photos = album.slice();
while (photos.length) console.log(photos.splice(Math.floor(Math.random() * photos.length),1)[0]);
!photos.length && (photos = album.slice()); // restore photos album and continue
while (photos.length) console.log(photos.splice(Math.floor(Math.random() * photos.length),1)[0]);
!photos.length && (photos = album.slice()); // restore photos album and continue
Following #Redu and my comments, take it out after you use it, but work on a copy.
var animations = ['fadeIn', 'fadeInDown', 'slideInUp', 'flipInY', 'bounceInLeft'];
var j;
var tmp = animations.slice(); //copy
var removed = 0;
for (var i = 1; i < 20; i++) {
j = Math.floor(Math.random() * tmp.length);
console.log(tmp[j]);
tmp.splice(j, 1);
removed++;
if (animations.length == removed) {
tmp = animations.slice();
removed = 0
}
}
I suggest to use a different method, by storing the last two selected elements and choose a different from the last selected items.
That prevent slicing and manipulation of original array.
function Random(array) {
var last = [];
this.next = function () {
var r;
do {
r = Math.floor(Math.random() * array.length);
} while (~last.indexOf(r))
last.length === 2 && last.shift();
last.push(r);
return array[r];
}
}
var animations = ['fadeIn', 'fadeInDown', 'slideInUp', 'flipInY', 'bounceInLeft'],
random = new Random(animations),
i;
for (i = 0; i < 15; i++) {
console.log(random.next());
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to sort and slice an array of objects

I have an array of shots. I have been able to take that array and loop through it to get all shots that occurred on hole #1 and then rearrange them in order based on "shot_number". I now need to do this for every hole and to create an array for each hole (ex: holeArray1, holeArray2). I have attempted a number of solutions to increment x but if I do I end up missing some shots that occurred on certain holes.
How can I refactor this function to create this array for every hole without just copying and pasting the code and changing the variable x myself? Thank you for your help. I know I should be able to figure this one out but am struggling.
$scope.createHoleShotsArrays = function () {
var i = 0;
var x = 1;
var holeArray = [];
var len = $scope.shots.length;
for (; i < len; i++) {
if ($scope.shots[i].attributes.hole == x) {
holeArray.push($scope.shots[i]);
holeArray.sort(function (a, b) {
if (a.attributes.shot_number > b.attributes.shot_number) {
return 1;
}
if (a.attributes.shot_number < b.attributes.shot_number) {
return -1;
}
// a must be equal to b
return 0;
});
}
}
console.log(holeArray);
};
Push the items you want into arrays, and sort them once. I don't have cases to test the code. You may modified it a little if something goes wrong.
$scope.createHoleShotsArrays = function() {
var holeArrays = [];
$scope.shots.forEach(function(shot) {
if (holeArrays.length < shot.attributes.hole) {
holeArrays[shot.attributes.hole - 1] = [];
}
holeArrays[shot.attributes.hole - 1].push(shot);
});
holeArrays.forEach(function(arr) {
arr.sort(function(a, b) {
return a.attributes.shot_number - b.attributes.shot_number;
});
});
console.log(holeArrays);
};

How to pick a random property from an object without repeating after multiple calls?

I'm trying to pick a random film from an object containing film objects. I need to be able to call the function repeatedly getting distinct results until every film has been used.
I have this function, but it doesn't work because the outer function returns with nothing even if the inner function calls itself because the result is not unique.
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var moviesLength = Object.keys(movies).length;
function doPick() {
var pick = pickRandomProperty(movies);
var distinct = true;
for (var i = 0;i < watchedFilms.length; i += 1) {
if (watchedFilms[i]===pick.title) {
distinct = false;
if (watchedFilms.length === moviesLength) {
watchedFilms = [];
}
}
}
if (distinct === true) {
watchedFilms.push(pick.title);
return pick;
}
if (distinct === false) {
console.log(pick.title+' has already been picked');
doPick();
}
};
return doPick();
}
T.J. Crowder already gave a great answer, however I wanted to show an alternative way of solving the problem using OO.
You could create an object that wraps over an array and makes sure that a random unused item is returned everytime. The version I created is cyclic, which means that it infinitely loops over the collection, but if you want to stop the cycle, you can just track how many movies were chosen and stop once you reached the total number of movies.
function CyclicRandomIterator(list) {
this.list = list;
this.usedIndexes = {};
this.displayedCount = 0;
}
CyclicRandomIterator.prototype.next = function () {
var len = this.list.length,
usedIndexes = this.usedIndexes,
lastBatchIndex = this.lastBatchIndex,
denyLastBatchIndex = this.displayedCount !== len - 1,
index;
if (this.displayedCount === len) {
lastBatchIndex = this.lastBatchIndex = this.lastIndex;
usedIndexes = this.usedIndexes = {};
this.displayedCount = 0;
}
do index = Math.floor(Math.random() * len);
while (usedIndexes[index] || (lastBatchIndex === index && denyLastBatchIndex));
this.displayedCount++;
usedIndexes[this.lastIndex = index] = true;
return this.list[index];
};
Then you can simply do something like:
var randomMovies = new CyclicRandomIterator(Object.keys(movies));
var randomMovie = movies[randomMovies.next()];
Note that the advantage of my implementation if you are cycling through items is that the same item will never be returned twice in a row, even at the beginning of a new cycle.
Update: You've said you can modify the film objects, so that simplifies things:
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
var counter = keyCount * 2;
// Try a random pick
while (--counter) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
// We've done two full count loops and not found one, find the
// *first* one we haven't watched, or of course return null if
// they've all been watched
for (counter = 0; counter < keyCount; ++counter) {
candidate = movies[keys[counter]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
return null;
}
This has the advantage that it doesn't matter if you call it with the same movies object or not.
Note the safety valve. Basically, as the number of watched films approaches the total number of films, our odds of picking a candidate at random get smaller. So if we've failed to do that after looping for twice as many iterations as there are films, we give up and just pick the first, if any.
Original (which doesn't modify film objects)
If you can't modify the film objects, you do still need the watchedFilms array, but it's fairly simple:
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
if (watchedFilms.length >= keyCount) {
return null;
}
while (true) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (watchedFilms.indexOf(candidate) === -1) {
watchedFilms.push(candidate);
return candidate;
}
}
}
Note that like your code, this assumes getRandomFilm is called with the same movies object each time.

JQuery Click event crashes program with no console errors

EDIT**
In a game I am creating I use the next question button to move onto other questions in the grid if the user is having trouble with the current one. At the moment I have had real problems with it as it keeps on crashing my program, and not giving any console errors. The last problem I had with it was that it said "too much recursion". Since then I thought I had sorted the problem, but I have just done a few tests and it crashes every time.
This is the click event for the button...
//Next question click event
$('.next-question').bind("click", function() {
$('td').removeClass('highlight-problem');
shuffleEqually(listOfWords);
shuffleEqually(nextWordIndexes);
var rndWord = nextWordIndexes[Math.floor(Math.random())];
var rndWord = nextWordIndexes[2];
//Adds and removes nesesary classes
$('td[data-word="' + listOfWords[rndWord].name + '"]').addClass('highlight-problem');
$('td[data-word=' + word + ']').removeClass('wrong-letter').removeClass('wrong-word').removeClass('right-letter');
var spellSpace = $('td[data-word="' + listOfWords[rndWord].name + '"]').hasClass('right-word');
if (spellSpace) {
$('.next-question').trigger('click');
} else {
$("#hintSound").attr('src', listOfWords[rndWord].audio);
hintSound.play();
$("#hintPic").attr('src', listOfWords[rndWord].pic);
$('#hintPicTitle').attr('title', listOfWords[rndWord].hint);
}
});
I think it may have something to do with the if statement, but have tried changing it to this..
if (spellSpace == false) {
$("#hintSound").attr('src', listOfWords[rndWord].audio);
hintSound.play();
$("#hintPic").attr('src', listOfWords[rndWord].pic);
$('#hintPicTitle').attr('title', listOfWords[rndWord].hint);
}
and it makes it even worse
ShuffleEqually:
//Shuffles words to randomize
shuffleEqually(nextWordIndexes);
var shuffledWords = [];
shuffledWords = chosenWords.sort(function () {
return 0.5 - Math.random();
});
function shuffleEqually(a1, a2) {
var arrays = [];
if (typeof a1 === 'object' && a1.length > 0) {
arrays.push(a1);
}
if (typeof a2 === 'object' && a2.length > 0) {
arrays.push(a2);
}
var minLength = arrays[0].length;
jQuery.each(arrays, function (i, a) {
minLength = a.length < minLength ? a.length : minLength;
});
var randoms = [];
for (i = 0; i < minLength; i++) {
randoms.push(Math.random());
}
jQuery.each(arrays, function (i, a) {
var i = minLength;
while (i--) {
var p = parseInt(randoms[i] * minLength);
var t = a[i];
a[i] = a[p];
a[p] = t;
}
});
};
Hint sound:
var hintSound = $("#hintSound")[0];
Your issue is an infinite loop, plain and simple.
$('.next-question').bind("click", function() {
// binds click...
...
if (spellSpace) {
$('.next-question').trigger('click');
// triggers click ON THE SAME ELEMENT COLLECTION (same selector)
You want to refine this. I assume you want the trigger to work on the next question, so I suggest changing the second statement to:
$(".next-question").eq(($(".next-question").index($(this)) + 1) % $(".next-question").length).trigger("click");
You have a second infinite loop in shuffleEqually:
jQuery.each(arrays, function (i, a) {
var i = minLength;
while (i--) {
var p = parseInt(randoms[i] * minLength);
var t = a[i];
a[i] = a[p];
a[p] = t;
}
Change the while condition to have a limiting value, or it will loop endlessly (as a decrement operation always succeeds).

Categories