How to refactor multiple For Loops into one? - javascript

How can you refactor this function? I feel like a nested for loop would work but I haven't figured out how to make it work.
Here's the function:
class Librarian {
constructor(name, library) {
this.name = name;
this.library = library;
}
findBook(bookTitle) {
for (var i = 0; i < this.library.shelves.fantasy.length; i++){
if (bookTitle === this.library.shelves.fantasy[i].title){
this.library.shelves.fantasy.splice(i, 1);
return `Yes, we have ${bookTitle}`;
}
} for (var i = 0; i < this.library.shelves.fiction.length; i++){
if (bookTitle === this.library.shelves.fiction[i].title){
this.library.shelves.fiction.splice(i, 1);
return `Yes, we have ${bookTitle}`;
}
} for (var i = 0; i < this.library.shelves.nonFiction.length; i++){
if (bookTitle === this.library.shelves.nonFiction[i].title){
this.library.shelves.nonFiction.splice(i, 1);
return `Yes, we have ${bookTitle}`;
}
} return `Sorry, we do not have ${bookTitle}`;
}
}
Here's my attempt:
findBook(bookTitle) {
for (var j = 0; j < this.libray.shelves.length; j++) {
for (var i = 0; i < this.library.shelves[i].length; i++){
if (bookTitle === this.library.shelves[j].title){
this.library.shelves[j].splice(i, 1);
return `Yes, we have ${bookTitle}`;
}
}
} return `Sorry, we do not have ${bookTitle}`;
}

Get the genres' names and then loop over them:
findBook(bookTitle) {
const genres = Object.keys(this.library.shelves);
for (var i = 0; i < genres.length; i++) {
const genre = genres[i];
for (var j = 0; j < this.library.shelves[genre].length; j++) {
if (bookTitle === this.library.shelves[genre][j].title) {
this.library.shelves[genre].splice(i, 1);
return `Yes, we have ${bookTitle}`;
}
}
}
return `Sorry, we do not have ${bookTitle}`;
}

It looks like you're really close to getting this to work, you just need to iterate over this.library.shelves[j].length in the second for loop instead of i. Then you need to access the ith member of the jth shelf.
findBook(bookTitle) {
for (var j = 0; j < this.libray.shelves.length; j++) {
for (var i = 0; i < this.library.shelves[j].length; i++){
if (bookTitle === this.library.shelves[j][i].title){
this.library.shelves[j].splice(i, 1);
return `Yes, we have ${bookTitle}`;
}
}
}
return `Sorry, we do not have ${bookTitle}`;
}

Related

A* Pathfinding, path not showing up

I have this code, in script (p5.js), I am running it and works as expected, but the moment I include the path code, that helps in finding the previous parents to form a path to the end goal, the browser crushes, as the images show. It fist colors the 3 cells then crashes. Here is the code.
var col = 12, row = 12, grid = new Array(col), openSet = [], closeSet = [], start, end, w, h, path = [];
function removefromArray(array_, element){
for(var i = array_.length - 1; i >= 0; i--){
if(array_[i] == element){
array_.splice(i, 1);
}
}
}
function heuristic(a, b){
var distance = abs(a.i - b.i) + abs(a.j - b.j);
return distance;
}
function Spot(i, j){
this.i = i;
this.j = j;
this.f = 0;
this.g = 0;
this.h = 0;
this.neighbor = [];
this.parent = undefined;
this.wall = false;
this.show = function (color){
fill(color);
if(this.wall){
fill(0);
}
noStroke();
rect(this.i * w, this.j * h, w - 1, h - 1);
}
this.addNeighbor = function(grid){
var i = this.i, j = this.j;
if(i < col - 1){
this.neighbor.push(grid[i+1] [j]);
}
if(i > 0){
this.neighbor.push(grid[i-1] [j]);
}
if(j < row-1){
this.neighbor.push(grid[i] [j+1]);
}
if(j > 0){
this.neighbor.push(grid[i] [j-1]);
}
}
}
function setup(){
createCanvas(500,500);
console.log("A*");
w = width / col;
h = height / row;
for( var i = 0; i< col; i++){
grid[i] = new Array(row);
}
//Adding a spot
for( var i = 0; i< col; i++){
for( var j = 0; j< row; j++){
grid[i][j] = new Spot(i,j);
}
}
//Adding a neighbor
for( var i = 0; i< col; i++){
for( var j = 0; j< row; j++){
grid[i][j].addNeighbor(grid);
}
}
start = grid[0][0];
end = grid[col - 1][row - 1];
openSet.push(start);
}
function draw(){
var winner = 0;
if(openSet.length > 0){
for( var i = 0; i< openSet.length; i++){
if(openSet[i].f < openSet[winner].f){
winner = i;
}
}
var current = openSet[winner];
if(current === end){
noLoop();
console.log("Done!");
}
removefromArray(openSet, current);
closeSet.push(current);
var neighbors = current.neighbor;
for(var i = 0; i < neighbors.length; i++){
var the_neighbor = neighbors[i];
if(!closeSet.includes(the_neighbor)){
var tempG = current.g + 1;
}
if(openSet.includes(the_neighbor)){
if(tempG < the_neighbor.g){
the_neighbor.g = tempG;
}
}
else{
the_neighbor.g = tempG;
openSet.push(the_neighbor);
}
the_neighbor.h = heuristic(the_neighbor, end);
the_neighbor.f = the_neighbor.g + the_neighbor.h;
the_neighbor.parent = current; // the previous node
}
}
else{
// no solution
}
background(0)
for( var i = 0; i< col; i++){
for( var j = 0; j< row; j++){
grid[i][j].show(color(255));
}
}
for( var i = 0; i< openSet.length; i++){
openSet[i].show(color("green"));
}
for( var i = 0; i< closeSet.length; i++){
closeSet[i].show(color("red"));
}
// path = [];
// var temp = current;
// path.push(temp);
// while(temp.parent){
// path.push(temp.parent);
// temp = temp.parent;
// }
// for(var i = 0; i < path.length; i++){
// path[i].show(color(0,0,255));
// }
}
If I try to remove the comments slash for this last part, the system will run for about 5secs then crashes. Someone with the solution, I will highly appreciate.
Result when the path part is uncommented
You are generating a cycle somehow in the chain of parents (i.e. Spot A has parent Spot B and Spot B has parent Spot A), which is causing an infinite loop. I'm not sure exactly where/why this is happening. Your code is a bit hard to read. You should avoid nondescript one letter variable & property names.
Also there are several scoping issues that may be causing unexpected behavior. The keyword var is the worst element of any programming language since goto and it should be scoured from the face of the internet with the fire of 1000 suns. Please use let. See the comments below for more explanation.
var col = 12,
row = 12,
grid = new Array(col),
openSet = [],
closeSet = [],
start, end, w, h, path = [];
function removefromArray(array_, element) {
for (let i = array_.length - 1; i >= 0; i--) {
if (array_[i] == element) {
array_.splice(i, 1);
}
}
}
function heuristic(a, b) {
var distance = abs(a.i - b.i) + abs(a.j - b.j);
return distance;
}
function Spot(i, j) {
this.i = i;
this.j = j;
this.f = 0;
this.g = 0;
this.h = 0;
this.neighbor = [];
this.parent = undefined;
this.wall = false;
this.show = function(color) {
fill(color);
if (this.wall) {
fill(0);
}
noStroke();
rect(this.i * w, this.j * h, w - 1, h - 1);
}
this.addNeighbor = function(grid) {
let i = this.i,
j = this.j;
if (i < col - 1) {
this.neighbor.push(grid[i + 1][j]);
}
if (i > 0) {
this.neighbor.push(grid[i - 1][j]);
}
if (j < row - 1) {
this.neighbor.push(grid[i][j + 1]);
}
if (j > 0) {
this.neighbor.push(grid[i][j - 1]);
}
}
}
function setup() {
createCanvas(500, 500);
console.log("A*");
w = width / col;
h = height / row;
for (let i = 0; i < col; i++) {
grid[i] = new Array(row);
}
//Adding a spot
for (let i = 0; i < col; i++) {
for (let j = 0; j < row; j++) {
grid[i][j] = new Spot(i, j);
}
}
//Adding a neighbor
for (let i = 0; i < col; i++) {
for (let j = 0; j < row; j++) {
grid[i][j].addNeighbor(grid);
}
}
start = grid[0][0];
end = grid[col - 1][row - 1];
openSet.push(start);
}
function draw() {
let winner = 0;
let current
if (openSet.length > 0) {
for (let i = 0; i < openSet.length; i++) {
if (openSet[i].f < openSet[winner].f) {
winner = i;
}
}
current = openSet[winner];
if (current === end) {
noLoop();
console.log("Done!");
}
removefromArray(openSet, current);
closeSet.push(current);
var neighbors = current.neighbor;
for (let i = 0; i < neighbors.length; i++) {
var the_neighbor = neighbors[i];
// The use of var here results in very weird behavior
// where tempG's value is preserved from the previous
// iteration of this loop even when it is not assigned
// a value. If that is desired you should declare this
// variable outside of the for loop.
//
// If you always use let isntead of var you will get
// errors instead of bizarre behavior. That will help
// you be deliberate about your variable scoping.
if (!closeSet.includes(the_neighbor)) {
var tempG = current.g + 1;
} else {
print('tempG not set');
}
if (openSet.includes(the_neighbor)) {
if (tempG < the_neighbor.g) {
the_neighbor.g = tempG;
}
} else {
print(`tempG: ${tempG}`);
the_neighbor.g = tempG;
openSet.push(the_neighbor);
print(`openSet: ${openSet.length}`);
}
the_neighbor.h = heuristic(the_neighbor, end);
the_neighbor.f = the_neighbor.g + the_neighbor.h;
the_neighbor.parent = current; // the previous node
}
} else {
// no solution
}
background(0)
for (let i = 0; i < col; i++) {
for (var j = 0; j < row; j++) {
grid[i][j].show(color(255));
}
}
for (let i = 0; i < openSet.length; i++) {
openSet[i].show(color("green"));
}
for (let i = 0; i < closeSet.length; i++) {
closeSet[i].show(color("red"));
}
path = [];
let temp = current;
path.push(temp);
while (temp.parent) {
if (path.includes(temp.parent)) {
print('Cycle detected!');
console.log({ current: { i: temp.i, j: temp.j }, parent: { i: temp.parent.i, j: temp.parent.j } });
break;
}
path.push(temp.parent);
temp = temp.parent;
}
for (let i = 0; i < path.length; i++) {
path[i].show(color(0, 0, 255));
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>
Update: Infinite Loop Fix
The part of your code that updates neighbors deviates from the definition of A* pretty substantially. Here's what I came up with (single letter variable names replaces with meaningful names):
let tentativePathScore = current.shortestPathScore + 1;
if (the_neighbor.heuristicScore === undefined) {
the_neighbor.heuristicScore = heuristic(the_neighbor, end);
}
if (the_neighbor.combinedScore === undefined ||
tentativePathScore + the_neighbor.heuristicScore < the_neighbor.combinedScore) {
// Update the path score and combined score for this neighbor.
the_neighbor.shortestPathScore = tentativePathScore;
the_neighbor.combinedScore = the_neighbor.shortestPathScore + the_neighbor.heuristicScore;
the_neighbor.parent = current; // the previous node
if (!openSet.includes(the_neighbor)) {
openSet.push(the_neighbor);
}
}
And here's a working snippet with walls added:
let col = 12,
row = 12,
grid = new Array(col),
openSet = [],
closeSet = [],
start, end, w, h, path = [];
function removefromArray(array_, element) {
for (let i = array_.length - 1; i >= 0; i--) {
if (array_[i] == element) {
array_.splice(i, 1);
}
}
}
function heuristic(a, b) {
if (a.wall) {
return Infinity;
}
return abs(a.i - b.i) + abs(a.j - b.j);
}
function Spot(i, j) {
this.i = i;
this.j = j;
this.combinedScore = undefined;
this.shortestPathScore = undefined;
this.heuristicScore = undefined;
this.neighbor = [];
this.parent = undefined;
this.wall = false;
this.show = function(color) {
fill(color);
if (this.wall) {
fill(0);
}
noStroke();
rect(this.i * w, this.j * h, w - 1, h - 1);
}
this.addNeighbor = function(grid) {
let i = this.i,
j = this.j;
if (i < col - 1) {
this.neighbor.push(grid[i + 1][j]);
}
if (i > 0) {
this.neighbor.push(grid[i - 1][j]);
}
if (j < row - 1) {
this.neighbor.push(grid[i][j + 1]);
}
if (j > 0) {
this.neighbor.push(grid[i][j - 1]);
}
}
}
function setup() {
createCanvas(500, 500);
console.log("A*");
w = width / col;
h = height / row;
for (let i = 0; i < col; i++) {
grid[i] = new Array(row);
}
//Adding a spot
for (let i = 0; i < col; i++) {
for (let j = 0; j < row; j++) {
grid[i][j] = new Spot(i, j);
}
}
//Adding a neighbor
for (let i = 0; i < col; i++) {
for (let j = 0; j < row; j++) {
grid[i][j].addNeighbor(grid);
}
}
// make walls
for (let i = 0; i < col; i++) {
for (let j = 0; j < row; j++) {
if ((i > 1 || j > 1) && (i < col - 2 || j < row - 2) && random() < 0.2) {
grid[i][j].wall = true;
}
}
}
start = grid[0][0];
end = grid[col - 1][row - 1];
start.shortestPathScore = 0;
start.heuristicScore = heuristic(start, end);
start.combinedScore = start.shortestPathScore + start.heuristicScore;
openSet.push(start);
}
function draw() {
let winner = 0;
let current;
if (openSet.length > 0) {
for (let i = 0; i < openSet.length; i++) {
if (openSet[i].combinedScore < openSet[winner].combinedScore) {
winner = i;
}
}
current = openSet[winner];
if (current === end) {
noLoop();
console.log("Done!");
}
removefromArray(openSet, current);
closeSet.push(current);
var neighbors = current.neighbor;
for (let i = 0; i < neighbors.length; i++) {
var the_neighbor = neighbors[i];
let tentativePathScore = current.shortestPathScore + 1;
if (the_neighbor.heuristicScore === undefined) {
the_neighbor.heuristicScore = heuristic(the_neighbor, end);
}
if (the_neighbor.combinedScore === undefined ||
tentativePathScore + the_neighbor.heuristicScore < the_neighbor.combinedScore) {
// Update the path score and combined score for this neighbor.
the_neighbor.shortestPathScore = tentativePathScore;
the_neighbor.combinedScore = the_neighbor.shortestPathScore + the_neighbor.heuristicScore;
the_neighbor.parent = current; // the previous node
if (!openSet.includes(the_neighbor)) {
openSet.push(the_neighbor);
}
}
}
} else {
// no solution
}
background(0)
for (let i = 0; i < col; i++) {
for (let j = 0; j < row; j++) {
grid[i][j].show(color(255));
}
}
for (let i = 0; i < openSet.length; i++) {
openSet[i].show(color("green"));
}
for (let i = 0; i < closeSet.length; i++) {
closeSet[i].show(color("red"));
}
path = [];
let temp = current;
path.push(temp);
while (temp.parent) {
if (path.includes(temp.parent)) {
print('Cycle detected!');
break;
}
path.push(temp.parent);
temp = temp.parent;
}
for (let i = 0; i < path.length; i++) {
path[i].show(color(0, 0, 255));
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>

window.location.reload(); stuck in infinite loop

I want this function to loop through all items, if it finds the right item to load its page, and if it doesn't find the right item it should reload the page and for loop again. When I delete the window.location.reload(); it loads normally to the item page. This is the code:
var item_name = "Washed";
var item_color = "Red";
function pickItem() {
let items = document.getElementsByClassName("name-link");
for(i = 0; i < items.length; i++) {
if((items[i].innerHTML).includes(item_name)) {
for(j = 0; j < items.length; j++) {
if((items[j].innerHTML).includes(item_color)) {
if(items[i].href == items[j].href) {
window.location.assign(items[i, j].href);
}
}
}
}
}
window.location.reload();
}
In the following form it works as I want, but why does it need the chrome.storage function to work?(I used it with the chrome.storage before, but it was too slow for my purposes so I had to change it.)
var item_name = "Washed";
var item_color = "Red";
function pickItem() {
let items = document.getElementsByClassName("name-link");
chrome.storage.sync.get(["itemName", "color"], function(data) {
for(i = 0; i < items.length; i++) {
if((items[i].innerHTML).includes(item_name)) {
for(j = 0; j < items.length; j++) {
if((items[j].innerHTML).includes(item_color)) {
if(items[i].href == items[j].href) {
window.location.assign(items[i, j].href);
chrome.storage.sync.set({"item_url": items[i, j].href});
}
}
}
}
}
})
window.location.reload()
}
I'd recommend adding a boolean variable which indicates if a location to navigate to has been found yet and wrap the call to window.location.reload() inside an if-block which checks the state of the variable.
e.g.
function pickItem() {
let items = document.getElementsByClassName("name-link");
let found = false;
for (i = 0; i < items.length; i++) {
if ((items[i].innerHTML).includes(item_name)) {
for (j = 0; j < items.length; j++) {
if ((items[j].innerHTML).includes(item_color)) {
if (items[i].href == items[j].href) {
found = true;
window.location.assign(items[i, j].href);
}
}
}
}
}
if (!found) {
window.location.reload();
}
}

I can't figure out why it's saying that the matcher function is undefined.

This code is designed to identify an array of anagrams for a string given an array of possible anagrams.
var anagram = function(input) {
return input.toLowerCase();
}
I'm adding the matcher function here to the String prototype.
String.prototype.matcher = function(remainingLetters) {
var clone = this.split("");
for (var i = 0; i < clone.length; i++) {
if (clone[i].indexOf(remainingLetters) > -1) {
remainingLetters.splice(clone[i].indexOf(remainingLetters, 1));
clone.splice(i, 1);
}
}
if (remainingLetters.length == 0 && clone.length == 0) {
return true;
}
else {
return false;
}
}
a
String.prototype.matches = function(matchWordArray) {
var result = [];
for (var i = 0; matchWordArray.length; i++) {
var remainingLetters = this.split("");
if (matchWordArray[i].matcher(remainingLetters)) {
result.push(arrayToMatch[i]);
}
}
return result;
}
var a = anagram("test");
a.matches(["stet", "blah", "1"]);
module.exports = anagram;
Should probably be:
for (var i = 0; i < matchWordArray.length; i++) {
The original statement:
for (var i = 0; matchWordArray.length; i++) {
...would result in an infinite loop because matchWordArray.length is always truthy (3) in your test.

Randomly getting Uncaught TypeError: Cannot read property 'substring' of undefined

success: function(data) {
var timeslots = new Array('09:00am', '09:15am', '09:30am', '09:45am', '10:00am', '10:15am', '10:30am', '10:45am', '11:00am', '11:15am', '11:30am', '11:45am', '02:00pm', '02:15pm', '02:30pm', '02:45pm', '03:00pm', '03:15pm', '03:30pm', '03:45pm', '04:00pm', '04:15pm', '04:30pm');
var booked = '';
var bookedSlots = [];
var t_slots;
if (data.bookedslots.length == timeslots.length) {
if (manageDay) {
$('.manageSlots .timeSlot').remove();
}
alert('No Available Slots');
$(where).find('select.time').empty();
return false;
}
for (var t = 0; t < data.bookedslots.length; t++) {
booked = data.bookedslots[t]['time'].substr(0, data.bookedslots[t]['time'].length - 3);
bookedSlots.push(booked);
}
for (var i = 0; i < timeslots.length; i++) {
for (var j = 0; j < bookedSlots.length; j++) {
///// getting the error on this line ////////if(timeslots[i].substring(0,5) === bookedSlots[j]){
timeslots.splice(i, 1);
}
}
}
for (var z = 0; z < timeslots.length; z++) {
t_slots += '<option value="' + timeslots[z] + '">' + timeslots[z] + '</option>';
}
}
The problem is that you have spliced out timeslots[i], but in the next iteration of j you try to use it. If it was the last item in the array you will get an error. If you splice, you should break on the next line to abort the rest of the j loop.
for (var i = 0; i < timeslots.length; i++) {
for (var j = 0; j < bookedSlots.length; j++) {
if(timeslots[i].substring(0,5) === bookedSlots[j]){
timeslots.splice(i, 1);
break;
}
}
}

Is there a single function to check an array contains another array?

[1,2,3].CONTAINS([1,2]) ==> true
[1,2,3].CONTAINS([1,2,3,4]) ==> false
or
{a:1,b:2,c:3}.HASKEYS([a,b]) ==> true
{a:1,b:2,c:3}.HASKEYS([a,b,c,d]) ==> false
Is there a single function to check an array contains another array?
No, but you can make one:
Array.prototype.contains = function(other) {
for (var i = 0; i < other.length; i++) {
if (this.indexOf(other[i]) === -1) return false;
}
return true;
}
And if order matters:
Array.prototype.contains = function(other) {
var broken;
if (!other.length) return true;
for (var i = 0; i < this.length - other.length + 1; i++) {
broken = false;
for (var j = 0; j < other.length; j++) {
if (this[i + j] !== other[j]) {
broken = true;
break;
}
}
if (!broken) return true;
}
return false;
}
The other function is similar, so I'll leave it to you to finish:
Object.prototype.has_keys = function(keys) {
...
}

Categories

Resources