Array not being updated after switching indexs - javascript

I'm working on a visualizer for sorting algorithms. Everything is working as intended until I got to the Selection Sort. I understand that the Selection sort will make a pass and search for the MINIMUM value and then swap that the index that it started at in the array. However, each time it makes a pass, the i value doesn't change. I tested it by changing the color of the block the i index represents in my loop and it never changes, so the MINIMUM value just keeps switching to where ever the i is.
You can view my project here on GitHub Pages, just use the left Navbar to choose Selection Sort and you can see the problem I'm having. The bottom snippet is my swap function, it didn't do this with any of the other sort methods, only the selection sort.
Github Pages -- https://kevin6767.github.io/sorting-algorithm-visualization/
Selection function
async function selectionSort() {
let blocks = document.querySelectorAll('.block');
for (let i = 0; i < blocks.length; i++) {
// Assume a minimum value
let min = i;
for (let j = i + 1; j < blocks.length; j++) {
blocks[j].style.backgroundColor = '#FF4949';
blocks[min].style.backgroundColor = '#13CE66';
blocks[i].style.backgroundColor = 'orange';
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, frame_speed)
);
const value1 = Number(blocks[j].childNodes[0].innerHTML);
const value2 = Number(blocks[min].childNodes[0].innerHTML);
if (value1 < value2) {
blocks[min].style.backgroundColor = '#58B7FF';
min = j;
}
blocks[j].style.backgroundColor = '#58B7FF';
}
if (min !== i) {
let tmp = blocks[i];
blocks[i] = blocks[min];
blocks[min] = tmp;
await swap(blocks[i], blocks[min]);
blocks = document.querySelectorAll('.block');
}
// Swap if new minimun value found
blocks[i].style.backgroundColor = '#58B7FF';
}
}
Swap function
function swap(el1, el2) {
return new Promise((resolve) => {
const style1 = window.getComputedStyle(el1);
const style2 = window.getComputedStyle(el2);
const transform1 = style1.getPropertyValue('transform');
const transform2 = style2.getPropertyValue('transform');
el1.style.transform = transform2;
el2.style.transform = transform1;
// Wait for the transition to end!
window.requestAnimationFrame(function () {
setTimeout(() => {
container.insertBefore(el2, el1);
resolve();
}, 300);
});
});
}

I ended up fixing it. It seems that I had to take the Nodelist array I was getting from just .querySelectorAll and convert that into an array using .Arrayfrom() which was pretty simple after some googling. From then on I needed to figure out how to update the array each pass, which once again was as simple as just moving one index from another.
The interesting part of the answer was how I was going to update the Nodelist itself that way all my css code would still work (This is a sorting visualizer, so it would show you what element it was on and highlight it with a color). The answer however was right in front of me. Even though I turned the Nodelist array into a regular array, I was still able to apply styles to it. This meant I didn't have to mutate the Nodelist array at all and was just able to keep a seperate array within the function to work with.
PS. The algorithm did have a lot of trouble in the above snippet because I was comparing 2 strings in the if statement (value1 and value2) this is what caused a lot of the actual algorithm erroring and was simply fixed by adding a Number() function around my innerhtml code.
Selection
async function selectionSort() {
let blocks = document.querySelectorAll('.block');
let convertedBlocks = Array.from(blocks);
let len = convertedBlocks.length;
for (let i = 0; i < len; i++) {
let min = i;
for (let j = i + 1; j < len; j++) {
convertedBlocks[j].style.backgroundColor = 'red';
convertedBlocks[min].style.backgroundColor = 'green';
convertedBlocks[i].style.backgroundColor = 'orange';
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, frame_speed)
);
if (
Number(convertedBlocks[min].childNodes[0].innerHTML) >
Number(convertedBlocks[j].childNodes[0].innerHTML)
) {
convertedBlocks[min].style.backgroundColor = '#58B7FF';
min = j;
}
convertedBlocks[j].style.backgroundColor = '#58B7FF';
}
if (min !== i) {
let tmp = convertedBlocks[i];
convertedBlocks[i] = convertedBlocks[min];
convertedBlocks[min] = tmp;
await swap(convertedBlocks[i], convertedBlocks[min]);
}
convertedBlocks[min].style.backgroundColor = '#58B7FF';
convertedBlocks[i].style.backgroundColor = '#58B7FF';
}
}

Related

Move an array in the place of another array

I am in the making of an apps script, that takes a specified range, divides it into subranges of 4 values each, and returns the array. It looks like this:
Code that follows that to divide it into said subranges:
function sort() {
const chunks = (range, size) =>
Array.from(
new Array(Math.ceil(range.length / size)),
(_, i) => range.slice(i * size, i * size + size)
);
let range = ranges.getValues().flat();
var array = chunks(range, 4)
}
}
}
}
Now, what I want to do with these, is that when someone removes data from one of the subranges, the ones below will go up, so that there are no gaps between the subranges. What I mean by that, is in the case of someone clearing data from, for example, the second subrange, all the ones that are below will go up.
NOTE: It is just a function that I later use in onChange(), and the ranges variable was called earlier throughout the code, and it's just a named range.
So far I've got this piece of code, but it doesn't seem to work:
for (let i = 0; i < array.length; i++) {
for (let j = 0; j < 4; j++) {
var equ = 0;
var mult = 0;
equ += array[i][j];
mult += array[i+1][j];
if (equ == 0 || "" && mult != "" && 0) {
array[i+1].copyTo(array[i], {contentsOnly:true})
}
}

How to process large array in javascript without blocking UI with chunk processor

I want to process a large array without blocking the UI, but I also need the processed array to reprint some features in a map. I have used the first approach in the most voted answer of this question: Best way to iterate over an array without blocking the UI
The problem is that when the function finish the first chunk and therefore make use of setTimeout, the thread is released and the map only repaint the first chunk_size elements, in this case 100 but I want all 1000 elements to be multiplied by 10.
I tried to use promises but I am missing something because the same problem occurs.
In this JSbin you can check what happens: https://jsbin.com/fatijosufu/edit?js,console
An array of 1000 elements is created and the processLargeArray function should multiply each element by 10. When the array is printed after calling the function, only the 100 first elements have been processed, this is, only the first chunk of the array.
const array1 = []
for (let i = 0; i < 1000; i++){
array1[i] = i;
}
function processLargeArray(array) {
// set this to whatever number of items you can process at once
var chunk = 100;
var index = 0;
function doChunk() {
var cnt = chunk;
while (cnt-- && index < array.length) {
array[index] = array[index] * 10;
++index;
}
if (index < array.length) {
// set Timeout for async iteration
setTimeout(doChunk, 1);
}
}
doChunk();
}
processLargeArray(array1);
console.log(array1);
The way I tried to solve the problem with promises, but that is not working: https://jsbin.com/fatijosufu/edit?js,console
const array1 = []
for (let i = 0; i < 1000; i++){
array1[i] = i;
}
function processLargeArray(array) {
return new Promise((resolve, reject) => {
// set this to whatever number of items you can process at once
var chunk = 100;
var index = 0;
function doChunk() {
var cnt = chunk;
while (cnt-- && index < array.length) {
array[index] = array[index] * 10;
++index;
}
if (index < array.length) {
// set Timeout for async iteration
setTimeout(doChunk, 1);
}
}
doChunk();
resolve(array);
});
}
processLargeArray(array1).then((array) => {
console.log(array);
});
A simpler method to keep the UI responsive would be to insert await someTimeout right in the processing loop, thus splitting it into several "micro tasks" and giving the UI a chance to update. Here's a working example:
BIG_ARRAY = Array(1000).fill(0)
function heavy(x) {
let y = 0;
for (let i = 0; i < 1e6; i++)
y += Math.sin(y);
return x + y;
}
function processAll() {
for (let x of BIG_ARRAY)
heavy(x)
}
function pause() {
return new Promise(r => setTimeout(r, 0))
}
async function processChunks() {
for (let [n, x] of BIG_ARRAY.entries()) {
heavy(x)
if (n % 10 === 0)
await pause()
}
}
<button onclick='processAll()'>all</button>
<button onclick='processChunks()'>chunks</button>
<button onclick='document.getElementById("test").innerHTML += "*"'>
test if UI is responsive
</button>
<p>Press 'all' or 'chunks' to start and then press 'test' periodically to test the UI.</p>
<p id="test"></p>
A cleaner, but a bit more complex alternative would be to use real threading, i.e. workers.

Trying to replace random array characters within an array

This is my first ever question, please give me feedback if I suck at this. Thank you
I am trying to create a JavaScript terminal game from a paid course. The game is called 'Find My Hat'.
One section of the problem requires me to create a static method called generateField(). This method generates a field, on the field there are 4 possible characters:
hat = '^';
hole = 'O';
fieldCharacter = '░';
pathCharacter = '*';
The fieldCharacter is the background for the game area. The game area is comprised of nested arrays. I haven't had problems creating the field, but I also want to replace random fieldCharacters with holes. I tried this with Math.random and nested loops to iterate over the nested arrays. I have not been able to figure out how to get this to work.
Here is the code below, sorry if I missed any details, I will try respond to everyone I can.
class Field {
constructor(field) {
this._field = field;
}
print() {
this._field = field.join('\n');
console.log(this._field.replace(/,/g, ''))
}
static generateField(height, width) {
let finalArray = []
for (let i = 0; i < height; i++) {
finalArray.push([fieldCharacter.repeat(width)]);
}
for (let i = 0; i < finalArray.length; i++) {
let randomHole = () => {
let result = Math.random() * width;
return result;
}
for (let j = 0; j < finalArray[i].length; j++) {
if (randomHole() > width - 2) {
finalArray[i][j] = hole;
}
}
}
return finalArray.join('\n').replace(/,/g, '');
}
}
console.log(Field.generateField(5, 10));
One Random Output:
O
O
░░░░░░░░░░
░░░░░░░░░░
O
Example for good output:
░░░░O░░░░░
░░░O░OO░░░
░░O░░░O░░░
░░░░O░░░░░
OO░░░░O░░O
I did some changes to your code and use the stackoverflow snippet to show you how it works
String.prototype.replaceAt = function(index, replacement) {
return this.substr(0, index) + replacement + this.substr(index + replacement.length);
}
hat = '^';
hole = 'O';
fieldCharacter = '░';
pathCharacter = '*';
class Field {
constructor(field) {
this._field = field;
}
print() {
this._field = field.join('\n');
console.log(this._field.replace(/,/g, ''))
}
static generateField(height, width) {
let finalArray = []
for (let i = 0; i < height; i++) {
finalArray.push(fieldCharacter.repeat(width));
}
//i dont know why you did this loop but i changed the inside for marking random holes
for (let i = 0; i < finalArray.length; i++) {
//pick random x and y
let randomHoleX = Math.floor(Math.random() * width);
let randomHoleY = Math.floor(Math.random() * height);
//becouse you save array of strings you need to find the correct array and replace the char at the correct index.
finalArray[randomHoleY] = finalArray[randomHoleY].replaceAt(randomHoleX, hole);
}
return finalArray.join('\n').replace(/,/g, '');
}
}
console.log(Field.generateField(5, 10));

JS Class property "is undefined" even tho it appears to be defined

I'm quite new to JS and I'm trying to do a TicTacToe game with a custom size board in an attempt to learn a bit more. I first coded just a 3x3 version and started building up from there.
Right as I got past the point where I have a custom grid size entered right after loading the page and the grid rendering, I started getting the same problem when trying to click any cell to try and play a turn.
"this.game_state[clicked_cell_i] is undefined".
I have tried opening up F12 and checking if the game_state array (which is a 2d array of strings that tracks which cell is played and which isn't) but when I do everything seems normal and the array gets printed out without problem. (picture showcases printing out the game_state array in a 4x4 grid) https://i.stack.imgur.com/gV0pY.png
I would really appreciate it if somebody could explain to me what's happening or even better - help me fix it. Thanks :)
Code: https://jsfiddle.net/z8649pxL
class game {
status_display;
is_game_active;
curr_player;
game_state;
constructor() {
this.status_display = document.querySelector('.status');
this.is_game_active = true;
this.curr_player = "X";
this.game_state = matrix(rows, rows, "");
}
cell_played(clicked_cell, clicked_cell_i, clicked_cell_j){
this.game_state[clicked_cell_i][clicked_cell_j] = this.curr_player;
clicked_cell.innerHTML = this.curr_player;
if(this.curr_player === "X"){
document.getElementById((i*rows)+j).style.backgroundColor = "#ff6600";
} else if(this.curr_player === "O"){
document.getElementById((i*rows)+j).style.backgroundColor = "#33ccff";
}
}
cell_click(clicked_cellEvent){
debugger
const clicked_cell = clicked_cellEvent.target;
let clicked_cell_i = parseInt(clicked_cell.getAttribute('i'));
let clicked_cell_j = parseInt(clicked_cell.getAttribute('j'));
if(this.game_state[clicked_cell_i][clicked_cell_j] !== "" || !this.is_game_active) {
return;
}
this.cell_played(clicked_cell, clicked_cell_i, clicked_cell_j);
this.res_validation();
}
};
let ex_game = new game();
function create_grid() {
document.getElementById('hidestart').style.display = "none";
var Container = document.getElementsByClassName("grid");
Container.innerHTML = '';
rows = prompt("n?");
let i = 0, j = 0;
document.documentElement.style.setProperty("--columns-row", rows);
for (i = 0; i < rows ; i++) {
for(j = 0; j < rows; j++){
var div = document.createElement("div");
div.className = "cell";
div.id = (i*rows)+j;
div.setAttribute("cell-index", (i*rows)+j);
div.setAttribute("i", i);
div.setAttribute("j", j);
let wrapper = document.getElementsByClassName("grid");
wrapper[0].appendChild(div);
}
}
document.querySelectorAll('.cell').forEach(cell => cell.addEventListener('click', (e) => {
ex_game.cell_click(e);
e.stopPropagation();
}));
document.getElementById('hidestart').style.display = "block";
}
function matrix(rows, cols, defaultValue){
var arr = [];
// Creates all lines:
for(var i=0; i < rows; i++){
// Creates an empty line
arr.push([]);
// Adds cols to the empty line:
arr[i].push(new Array(cols));
for(var j=0; j < cols; j++){
// Initializes:
arr[i][j] = defaultValue;
}
}
return arr;
}```
When you call
new game()
the constructor is been called of that class.
And in that constructor you are calling matrix which requires the value of rows.
But initially the class do not have any value.
So the state is not getting initialized at that point of time.
That why when you use the array, it is getting undefined.
So to resolve this issue, you could just try to get the object of class game after getting the rows from user, and pass that rows in the arguments when calling the constructor of the class.
let ex_game = new game(rows);
And then using this argument call the function matrix.
Edit:
I have looked into your code.
The error is in the method called cell_played. you are using i and j which are not known to it. Please replace your code with following line. This would resolve your error.
cell_played(clicked_cell, clicked_cell_i, clicked_cell_j){
this.game_state[clicked_cell_i][clicked_cell_j] = this.curr_player;
clicked_cell.innerHTML = this.curr_player;
if(this.curr_player === "X"){
document.getElementById((clicked_cell_i*rows)+clicked_cell_j).style.backgroundColor = "#ff6600";
} else if(this.curr_player === "O"){
document.getElementById((clicked_cell_i*rows)+clicked_cell_j).style.backgroundColor = "#33ccff";
}
}
And also remove the parameter rows from constructor and just call the constructor after taking the number of rows from user.
let ex_game
function create_grid() {
/* document.getElementById('hidestart').style.display = "none" */
var Container = document.getElementsByClassName("grid");
Container.innerHTML = '';
rows = prompt("n?");
let i = 0, j = 0;
ex_game= new game().........

How to dynamically change width of a headline to ensure even line lengths

I'm trying to figure out how The Washington Post website forces the main headline to break so nicely no matter the actual text or screen width. From what I can determine, if the h1 has a class of headline, its width is dynamically calculated to achieve the effect. I know this is done with JavaScript and imagine it's not hard-coded so how is that width actually computed to be somehow cognizant of how the headline will break (preventing highly variable line lengths, orphans, etc.). I just can't track down the JS that's controlling this functionality.
Here is an example of the page loading normally:
And an example with the headline class removed:
(You can also notice the difference right when the page loads, presumabley just before the JavaScript kicks in.)
Thanks!
This is what you want. Obviously, because some words are longer than others, not all the lines have perfectly the same length. This code below preserves the amount of line breaks that are created by the browser. Then, it adjusts the line breaks' position to ensure a more or less even line lengths.
P.S.: Try running the below code without JS and you'll see the difference
let h1 = document.querySelector('h1')
let text = h1.textContent
let splitText = text.split(' ')
let getContentBoxHeight = elem => {
let elemStyle = window.getComputedStyle(elem)
let elemHeightWithPadding = parseInt(elemStyle.getPropertyValue('height'))
let elemPadding = parseInt(elemStyle.getPropertyValue('padding-top')) + parseInt(elemStyle.getPropertyValue('padding-bottom'))
return elemHeightWithPadding - elemPadding
}
let getContentBoxWidth = elem => {
let elemStyle = window.getComputedStyle(elem)
let elemWidthWithPadding = parseInt(elemStyle.getPropertyValue('width'))
let elemPadding = parseInt(elemStyle.getPropertyValue('padding-left')) + parseInt(elemStyle.getPropertyValue('padding-right'))
return elemWidthWithPadding - elemPadding
}
// return the number of line breaks created by the browser
let breakPointAmount = (() => {
let body = document.querySelector('body')
let dummyH1 = document.createElement('h1')
let oneLineHeight
let totalLineHeight = getContentBoxHeight(h1)
dummyH1.appendChild(document.createTextNode('M'))
body.appendChild(dummyH1)
oneLineHeight = getContentBoxHeight(dummyH1)
dummyH1.remove()
return Math.round(totalLineHeight / oneLineHeight) - 1
})()
// refine the number of line breaks created by the browser
let breakPoints = (() => {
let denominator = breakPointAmount + 1
let points = []
let h1Length
h1.style.width = 'max-content'
h1Length = getContentBoxWidth(h1)
h1.style.width = ''
for (let i = 0; i < breakPointAmount; i++) {
points.push(Math.round(h1Length * (i + 1) / denominator))
}
return points
})()
// determine where that same number of break points should go in text
let indexesToBreak = Array(breakPointAmount).fill(1)
let cumulativeLength = 0
let cumulativeText = ''
for (let i = 0; i < splitText.length; i++) {
let word = splitText[i]
let calculateLength = word => {
let body = document.querySelector('body')
let dummyH1 = document.createElement('h1')
let length
dummyH1.appendChild(document.createTextNode(word))
dummyH1.style.width = 'max-content'
body.appendChild(dummyH1)
length = getContentBoxWidth(dummyH1)
dummyH1.remove()
return length
}
cumulativeText += word + ' '
cumulativeLength = calculateLength(cumulativeText)
for (let j = 0; j < indexesToBreak.length; j++) {
if (indexesToBreak[j] === 1) {
indexesToBreak[j] = {
index: i,
currentMin: Math.abs(cumulativeLength - breakPoints[j])
}
} else {
if (cumulativeLength - breakPoints[j] < indexesToBreak[j].currentMin) {
indexesToBreak[j] = {
index: i,
currentMin: Math.abs(cumulativeLength - breakPoints[j])
}
}
}
}
}
// insert break points at updated locations into text
let newText = (function() {
let final = ''
let itbIndex = 0
for (let i = 0; i < splitText.length; i++) {
final += `${splitText[i]} `
if (indexesToBreak[itbIndex] && i === indexesToBreak[itbIndex].index) {
final += '<br>'
itbIndex += 1
}
}
return final.trim()
})()
// add text with new break points to page
h1.innerHTML = newText
h1 {
text-align: center;
}
<h1>Some Long Title that Requires Line Breaking To Ensure Even Line Lengths</h1>

Categories

Resources