BFS Tree Traversal - javascript

I'm trying to write a Breadth-First-Search algorithm in Javascript that also includes the number of repetitions of every node. For example, in a videogame crafting menu, I want to craft a desired item A that requires items B x20, C x30, and D x100. Items B, C, and D in turn require other items times another arbitrary amount. I want to know how many of the items at the bottommost level of the tree I would need in order to craft item A. Try as I might, I cannot find a solution for this. Basically, I am doing a BFS of the materials and a BFS of the material amounts "in parallel". The intended output is a 2D array (2 rows, x columns) with the (unique) material names in the first row and the associated material amounts in the second row. This current code gives me a blank result. I've written 10-20 variations of it by now over the course of 8 weeks after having searched StackOverflow, https://www.w3schools.com/jsref/jsref_obj_array.asp, https://www.geeksforgeeks.org/java-util-hashmap-in-java/, and https://developer.mozilla.org/en-US/docs/Web/JavaScript. No luck, or I am just so new to JS that I don't see an answer that's right in front of me. All of the functions I have called from this function work perfectly (I tested them separately).
function traversebreadth(key, keyamt, leaveschecked, repschecked, leavesqueue, repsqueue, roots, leaves, leafreps) { //initial parameters: string, number, [], [], string (same as key), number (same as keyamt), Array (column vector), 2D array (each row is a material list corresponding to the same row in "roots"), 2D array (each row is a material amount list corresponding to the same row in both "roots" and "leaves")
var keyleaves = getleaves(key, roots, leaves); //gets the leaves(/children) of the current node
var keyreps = getleafreps(key, roots, leafreps); //gets the children of the current node's repetitions (or "amounts")
keyreps = scalarmultiply(keyamt, keyreps); //multiplies the current repetitions by the number of repetitions of the parent node
leaveschecked += key; //push the key to the queue of checked nodes
repschecked += keyamt; //push the keyamt to the queue of checked node reps
if(!Array.isArray(leavesqueue)){ //ensure leavesqueue is an Object (Array), not a Number
var lq = [];
lq.push(leavesqueue);
leavesqueue = lq;
}
if(!Array.isArray(repsqueue)){ //ensure repsqueue is an Object (Array), not a Number
var rq = [];
rq.push(repsqueue);
repsqueue = rq;
}
if(isemptyarray(leavesqueue,"row")){ //if leavesqueue is empty, then there are no more leaves to check and this recursive function can return the result
return ArrayLib.transpose(leaveschecked).concat(ArrayLib.transpose(repschecked)); //return all of the unique nodes needed to craft item A, and the total amount of each node
}else{ //else, repeat this function
var newleavesqueue = queueleaves(keyleaves, leavesqueue); //queueleaves() appends keyleaves to leavesqueue and removes the first element of leavesqueue, then returns leavesqueue
var newrepsqueue = queueleafreps(keyreps,repsqueue); //queueleafreps() does the same thing as queueleaves() using keyreps and repsqueue
var newkey = ArrayLib.transpose(newleavesqueue).shift(); //gets the new node to use as current key
var newkeyamt = ArrayLib.transpose(newrepsqueue).shift(); //gets the reps of this new current key
traversebreadth(newkey, newkeyamt, leaveschecked, repschecked, newleavesqueue, newrepsqueue, roots, leaves, leafreps); //repeat this function with the new parameters
}

Related

Use a value on an HTML checkbox as an already declared array? Vanilla JavaScript

So I have been having trouble finishing a function I would need for generating stuff for tabletop games. I have checkboxes in my HTML for a user to selected which things they want to be generated. The function is meant to take an multidimensional array, and iterate over, and randomly picking one piece of data from each layer of the array passed through it. Which when I just use an array it works.
JavaScript
// Checkbox Select Function Variables
const cb = document.querySelectorAll('[name="arrays"]')
let selected = [];
let count = 0
for(var i = 0; i < cb.length; i++){
if(cb[i].checked){
selected.push(cb[i].value)
}
} // End of chekcbox select for loop
do{
// Array Parse Function Variables
let arrayItem = selected[count]; // Stores the index value of selected equal to count
let res = Math.floor(Math.random()*arrayItem.length); // The RNG to pick from the array
The problem is getting the individual checkboxes to mean the corresponding array. Currently I was setting the value of each checkbox to the name of array needed and pushing that onto a new variable and I was hoping that the values would just act like the already declared arrays in my file. But I think they are just strings, not my arrays. everything about my function works except for selecting arrays used according to the checked boxes and my research has not found anything that works so far.
I don't know if using the value in the checkboxes is the best way of doing this, but I do need some way for each checkbox to correspond with my arrays.
HTML
<div class="arraySelect">
<label for="villainMotive">Villain Motive
<input type="checkbox" name="arrays" value="villainMotive" id="villainMotive">
</label><br>
<label for="villainMethod">Villain Method
<input type="checkbox" name="arrays" value="villainMethod" id="villainMethod">
</label><br>
<label for="villainWeakness">Villain Weakness
<input type="checkbox" name="arrays" value="villainWeakness" id="villainWeakness">
</label><br>
</div> <!-- End of arraySelect -->
<div class="arrayButton">
<button type="button">Array One</button>
</div><!--End of arrayButton-->
<div class="arrayOneText">
</div><!--End of arrayOneText-->
Full JavaScript
// HTML Selectors
const arrayButton = document.querySelector("button"); // Grabs HTML button needed
const arrayOneText = document.querySelector(".arrayOneText"); // Grabs the HTML text field to display the RNG text
// Array Parse Function
arrayButton.addEventListener("click", function(){ // Button click starts function
// Checkbox Select Function Variables
const cb = document.querySelectorAll('[name="arrays"]')
let selected = [];
let count = 0
for(var i = 0; i < cb.length; i++){
if(cb[i].checked){
selected.push(cb[i].value)
}
} // End of chekcbox select for loop
do{
// Array Parse Function Variables
let arrayItem = selected[count]; // Stores the index value of selected equal to count
let res = Math.floor(Math.random()*arrayItem.length); // The RNG to pick from the array
let setArray = arrayItem[res]; // Variable to check if first RNG result contains another array
let first = setArray[0]; // Store first index of setArray
let second = setArray[1]; // Stores second index of setArray, should always be another array
let arrayCount = 0 // Count for iterations of loop, used to break loop if stuck in it
let product = [setArray[0]]; // Variable to store seperated array values, then display on webpage
// Array Parse MAIN
do{
// setArray index[1] array check
if (Array.isArray(setArray[1])){ // Checks if setArray had another array at index 1
res = Math.floor(Math.random()*second.length); // Runs the RNG for second
setArray = second[res]; // Overwrites setArray to be the new RNG result from second
first = setArray[0]; // Overwrites first to be the new index 0 of setArray
second = setArray[1]; // Overwrites second to be index 1 of setArray
} else{
product = setArray; // Overwrites product to be correct insted of the push of first
break; // Exits loop completely
} // End of setArray index[1] array check if else
// setArray array check
if (Array.isArray(setArray)){ // Checks if setArray is an array
product.push(first); // Adds value of first to product if setArray is another array
} else {
product.push(setArray); // Adds value of setArray to product
} // End of setArray array check if else
arrayCount++ // Iterates arrayCount each complete do loop done
if (arrayCount === 15) break; // Checks arrayCount to prevent an infinite loop and break out of it
} // End of do
while (Array.isArray(second)); // Reruns do loop while second is an array at the end of the loop
// Array Parse Formating
if (Array.isArray(product)){ // Checks if product is an array
arrayOneText.innerText = product.join(" - "); // Displays and formats product if it is an array on the page
} else {
arrayOneText.innerText = product; // Displays product on the page
} // End of product array check if else
count++ // Iterates count each complete do loop done
if (count === selected.length) break; // Checks count to prevent an infinite loop and break out of it
} // End of checkbox select do while
while (count < selected.length);
}) // End of Array Parse
Sample array 1
const villainMotive = [
["Immortality",
["Acquire a legendary item to prolong life",
"Ascend to Godhood",
"Become undead or obtain a younger body",
"Steal a planar creature essence"]],
["Influence",
["Seize a position of power or title",
"Win a contest or tournament",
"Win favor with a powerful individual",
"Place a pawn into a position of power"]],
["Magic",
["Obtain an ancient artifact",
"Build a construct or magical device",
"Carry out a deity's wishes",
"Offer a sacrifice to a deity",
"Contact a lost deity or power",
"Open a gate to another world"]],
Sample Array 2
const villainMethod = [
["Agricultural Devastation",
["Blight",
"Crop Failure",
"Drought",
"Famine"]],
"Assualt or Beatings",
"Bounty Hunting",
"Assassination",
["Captivity",
["Slavery",
"Shackling",
"Imprisonment",
"Kidnapping"]],
Sample array 3
const villainWeakness = [
"A hidden object that holds the villain's soul",
"The villain's power is broken if the death of their true love is avenged",
"The villain is weakened in the presence of a particular artifact",
"A special deals extra damage when used against the villain",
"The villain is destroyed if it speaks its true name",
"An ancient prophecy or riddle reveals how the villain can be overthrown",
"The villain fails when an ancient enemy forgives its past actions",
"The villain loses its power if a mystic bargain it struck long ago is completed"
]

Google Sheets: Comparing Columns with Checkboxes

I am trying to create a to do list spreadsheet. In one column, "Daily Tasks", I have a list of tasks. In the other column, I have checkboxes. My goal is to create a script that will add all of the "checked" tasks to an array.
I am attempting to do this in my script using a nested for loop. However, when I log my new array that should contain the checked items (trueArr), I see just a series of what appear to be empty arrays.
How do I change my script so that my array contains the checked items from my spreadsheet?
Here is a link to my spreadsheet
Here is my code:
//spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
//worksheet
var todoList = ss.getSheetByName("To-Do List");
function completedArray(){
var tasks = todoList.getRange("c6:d22").getValues();
var checks = todoList.getRange("b6:b22").getValues();
var trueArr = [];
for(var i =0; i <tasks.length; i++){
for(var j=0; j<checks.length;j++){
if(checks[j]===true){
trueArr.push(tasks[j])
}
}
Logger.log(trueArr);
}
}
In my log, I expect to see the same items that are on my "Daily Tasks" list on my spreadsheet, but instead I only see empty arrays.
Explanation / Issues:
You are very close besides the following two things:
The checks array is a 2 dimensional array since you use getValues() to create it. Meaning that checks[j] is a row or in other words a 1 dimensional array. It is fundamentally wrong to compare an array with a value. Instead you should be using checks[j][0] in the if statement since this will iterate over each value of the single column. Another approach would be to use flat to convert the 2D array to 1D and then use your current code.
The second issue has to do with a for loop that you don't use anywhere. Your code iterates over i but you don't use or need i in your code. Keep also in mind that tasks and checks have the same number of rows (same length), therefore one for loop is enough in this scenario.
Solution:
//spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
//worksheet
var todoList = ss.getSheetByName("To-Do List");
function completedArray(){
var tasks = todoList.getRange("c6:d22").getValues();
var checks = todoList.getRange("b6:b22").getValues();
var trueArr = [];
for(var j=0; j<checks.length;j++){
if(checks[j][0]===true){
trueArr.push(tasks[j])
}
}
Logger.log(trueArr);
}
The structure of trueArr would be: [[task2, task2], [task3, task3], [task9, task9]] since tasks[j] is also a row or 1D array. This is why you will end up with a collection of 1D arrays.
I found a way to create the same array of checked items using the filter method and an arrow function. Made a two dimensional array that includes both checkboxes and list items. Then I filtered by the checkboxes in the first column. No need for loops!
//spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
//worksheet
var todoList = ss.getSheetByName("To-Do List");
var dailies = todoList.getRange("b6:e22").getValues();
function checkedItems(){
var checkedItems = dailies.filter(row => row[0] === true);
Logger.log(checkedItems);
}
Got the same result with a fraction of the code!

When returning a 2D array from a custom Google Sheets function, the values that are retrieved from a class are left blank

I'm sorry if the title is a bit confusing. Basically I have a custom Google Sheets function that receives a cell range as the argument. I have a class names Employee with a constructor that takes an ID as an argument and saves the value in a variable. In the custom function I create an array called Employees and for each value in the range I add a new instance of the Employee class with that value as the constructor argument. I then create an empty array called IDs and loop through each element in the Employees array and add the ID value saved in the class to the new array. If I return the IDs array, then when I call the function on google sheets, the list of items will appear vertically. However, if I instead create a 2D array and add the IDs variable as one of the elements while creating a new array with fixed elements as the second value. If I return that new 2D array, when I call the function the Ids array should be printed horizontally on the adjacent cells and the values of the second array should be printed underneath. However, the IDs are not printed, only the values of the second array are printed. If after looping through the Employees array and adding their Ids to the IDs array I add a a constant value, then only that value is printed when calling the function. This only happens when I return a 2D array, If I just return the IDs, they are all printed.
In the function bellow, when calling the chart function on Google Sheets, the ids array will be left blank on the sheet.
class Employee{
constructor(employee){
this.employeeID = employee;
}
}
function chart(employeeIDs){
employees = [];
for(x = 0; x < employeeIDs.length; x++)
employees.push(new Employee(employeeIDs[x]));
ids = [];
for(x = 0; x < employees.length; x++)
ids.push(employees[x].employeeID);
vehicles = ["Car", "road", "bus", "Plane", "Submarine"];
return [ids, vehicles];
}
If I change the return to...
return ids
Then when the function is called the list of items in ids are printed.
If I add additional values to the ids array after adding the values from the Employee class, such as...
ids = [];
for(x = 0; x < employees.length; x++)
ids.push(employees[x].employeeID);
ids.push(12)
vehicles = ["Car", "road", "bus", "Plane", "Submarine"];
return [ids, vehicles];
'''
Then only the number 12 is printed at the end, plus the entire contents of the vehicles array.

What is a faster way to write this function to delete rows/objects in a table?

So, I have this function that, after an update, deletes elements from a table. The function, lets call it foo(), takes in one parameter.
foo(obj);
This object obj, has a subfield within called messages of type Array. So, it would appear something like this:
obj.messages = [...];
Additionally, inside of obj.messages, each element contains an object that has another subfield called id. So, this looks something like:
obj.messages = [{to:"You",from:"Me",id:"QWERTY12345.v1"}, ...];
Now, in addition to the parameter, I have a live table that is also being referenced by the function foo. It uses a dataTable element that I called oTable. I then grab the rows of oTable and copy them into an Array called theCurrentTable.
var theCurrentTable = oTable.$('tr').slice(0);
Now, where it gets tricky, is when I look into the Array theCurrentTable, I returned values appear like this.
theCurrentTable = ["tr#messagesTable-item-QWERTY12345_v1", ...];
The loop below shows how I tried to show the problem. While it works (seemingly), the function itself can have over 1000 messages, and this is an extremely costly function. All it is doing is checking to see if the current displayed table has the elements given in the parameter, and if not a particular element, delete it. How can I better write this function?
var theCurrentTable = oTable.$('tr').slice(0);
var theReceivedMessages = obj.messages.slice(0);
for(var idx = 0; idx < theCurrentTable.length; idx++){ // through display
var displayID = theCurrentTable[idx].id.replace('messagesTable-item-','').replace('_','.');
var deletionPending = true;
for(var x = 0; x < theReceivedMessages.length; x++){
var messageID = theReceivedMessages[x].id;
if(diplayID == messageID){
console.log(displayID+' is safe...');
deletionPending = false;
}
}
if(deletionPending){
oTable.fnDeleteRow(idx);
}
}
I think I understand your problem. Your <tr> elements have an id that should match an item id within your messages.
First you should extract the message id values you need from the obj parameter
var ids = obj.messages.map(function (m) { return '#messagesTable-item-' + m.id; });
This will give you all the rows ids you need to keep and then join the array together to use jQuery to select the rows you don't want and remove them.
$('tr').not(ids.join(',')).remove();
Note: The Array.prototype.map() function is only supported from IE9 so you may need to use jQuery.map().
You could create a Set of the message ID values you have, so you can later detect if a given ID is in this Set in constant time.
Here is how that would look:
var theCurrentTable = oTable.$('tr').slice(0);
var theReceivedMessages = obj.messages.slice(0);
// Pre-processing: create a set of message id values:
var ids = new Set(theReceivedMessages.map( msg => msg.id ));
theCurrentTable.forEach(function (row, idx) { // through display
var displayID = row.id.replace('messagesTable-item-','').replace('_','.');
// Now you can skip the inner loop and just test whether the Set has the ID:
if(!ids.has(displayId)) {
oTable.fnDeleteRow(idx);
}
});
So now the time complexity is not any more O(n.m) -- where n is number of messages, and m the number of table rows -- but O(n+m), which for large values of n and m can make quite a difference.
Notes:
If theCurrentTable is not a true Array, then you might need to use a for loop like you did, or else use Array.from(theCurrentTable, function ...)
Secondly, the implementation of oTable.fnDeleteRow might be that you need to delete the last rows first, so that idx still points to the original row number. In that case you should reverse the loop, starting from the end.

How can I find a random number not located in an array? Want to loop through random numbers until one is found

I have a folder containing 10 images (img-1.jpg, img-2.jpg, ...). I currently feature 6 images on a site and I am trying to randomly swap out one of the 6 images with a different one that is not already shown on the page.
I have an array of all of my images (fullList), then I generate and array of the images that are currently shown (currentList).
Where I am having an issue is looping through the currentList array until I find a randomly generated item that is not currently in the currentList array. Then I will chose a random image from the page and change the image.
What I have now:
function sawpHeadshots() {
var fullList = [1,2,3,4,5,6,7,8,9,10];
var currentList = [];
$('#headshots li').each(function() {
currentList.push(parseInt($(this).css('background-image').replace(/\D+/, '')));
});
function generateRandom() {
return fullList[Math.floor(Math.random() * fullList.length)];
}
var rand = generateRandom();
/* not sure how to proceed at this point. */
}
Create a copy of the array randomly sort it, and remove them from the array when you create it. No need to keep generating random numbers or keeping track what was used.
var fullList = [1,2,3,4,5,6,7,8,9,10];
var random = fullList.slice(0).sort(function() {
return .5 - Math.random();
});
//get value with pop()
var current = random.pop();

Categories