Build a Sorting Visualizer - Step 19

Tell us what’s happening:

I cannot get step 19 to pass despite my output in the DOM seeming to be correct:

After you click #sort-btn , each div within #array-container should contain five span , each with a number as its text, and arranged to represent the steps required by Bubble Sort algorithm to sort the starting array.

I have confirmed my program runs the same as the example project:

Please have a look at my DOM output below. As far as I can tell, it should be valid.

Your code so far

script.js
const genBtn = document.getElementById('generate-btn');
const sortBtn = document.getElementById('sort-btn');
const startingArray = document.getElementById('starting-array');
const arrayContainer = document.getElementById('array-container');

const testArr = [17, 2, 78, 36, 84];
let count = 0;

// Return a random number between 1-100
const generateElement = () => {
    return Math.floor(Math.random() * 100 + 1);
}

// Return an array with 5 random numbers
const generateArray = () => {
    return Array.from({ length: 5 }, () => generateElement());
}

// Create and return an empty div
const generateContainer = () => {
    return document.createElement('div');
}

// Fill an HTML element with provided array values
const fillArrContainer = (container, arr) => {
    container.innerHTML = '';

    for (const num of arr) {
        const span = document.createElement('span');
        span.textContent = num;
        container.appendChild(span);
    }
}

// Return a boolean indicating if the first integer is 
// less than or equal to the second
const isOrdered = (a, b) => a <= b;

// Swap two sequential elements in an array if the first is larger than the second
const swapElements = (arr, i) => {
    if (!isOrdered(arr[i], arr[i + 1])) {
        const temp = arr[i];
        arr[i] = arr[i + 1];
        arr[i + 1] = temp;
    }
}

// Set a border on two elements being compared
const highlightCurrentEls = (el, i) => {
    const children = Array.from(el.children);

    children[i].style.border = "dashed 2px red";
    children[i + 1].style.border = "dashed 2px red";
}

// Recursively sort an array
const bubbleSortStep = (arr, i = 0, swapped = false) => {
    if (i >= arr.length - 1) {
        // Base case, array sorted
        if (!swapped) {
            return;
        }

        // Otherwise start new pass
        bubbleSortStep(arr, 0, false);
        return;
    }

    // List of array div elements currently within #array-container
    const divList = Array.from(arrayContainer.children);
    const arrList = [];

    // Get the most recent iteration (div array of span elements)
    // Extract values of span elements within in divs to array
    for (const div of divList) {
        const arrVal = Array.from(div.children).map(span => Number(span.textContent));
        arrList.push(arrVal);
    }

    // Highlight current comparison pair
    highlightCurrentEls(divList[count], i);

    // Swap elements if applicable
    if (!isOrdered(arrList[count][i], arrList[count][i + 1])) {
        swapElements(arrList[count], i);
        swapped = true;
    }

    // Fill new container with swapped array values and add to list of iterations
    const nextContainer = generateContainer();
    fillArrContainer(nextContainer, arrList[count]);
    arrayContainer.appendChild(nextContainer);

    // Move on to next comparison pair
    bubbleSortStep(arrList[count++], i + 1, swapped);
    return;
}

// Adds a green border to the final, sorted array div element
const highlightFinalArray = () => {
    const finalArray = Array.from(arrayContainer.children).pop();
    finalArray.style.border = "4px solid darkgreen";
}

genBtn.addEventListener('click', () => {
    count = 0;

    const arrayList = Array.from(arrayContainer.children);
    startingArray.innerHTML = "";

    // Reset #array-container if sorting has already occurred
    if (arrayList.length > 1) {
        arrayContainer.innerHTML = "";
        const newStartingArray = generateContainer();
        newStartingArray.id = 'starting-array';
        arrayContainer.append(startingArray);
        fillArrContainer(startingArray, generateArray());
    }

    // fillArrContainer(startingArray, generateArray());
    fillArrContainer(startingArray, testArr);
});

sortBtn.addEventListener('click', () => {
    const arr = Array.from(startingArray.children).map(el => Number(el.textContent));

    bubbleSortStep(arr);
    highlightFinalArray();
});
DOM output
<div id="array-container">
    <div id="starting-array">
        <span style="border: 2px dashed red;">17</span>
        <span style="border: 2px dashed red;">2</span>
        <span>78</span>
        <span>36</span>
        <span>84</span>
    </div>
    <div>
        <span>2</span>
        <span style="border: 2px dashed red;">17</span>
        <span style="border: 2px dashed red;">78</span>
        <span>36</span>
        <span>84</span>
    </div>
    <div>
        <span>2</span>
        <span>17</span>
        <span style="border: 2px dashed red;">78</span>
        <span style="border: 2px dashed red;">36</span>
        <span>84</span>
    </div>
    <div>
        <span>2</span>
        <span>17</span>
        <span>36</span>
        <span style="border: 2px dashed red;">78</span>
        <span style="border: 2px dashed red;">84</span>
    </div>
    <div>
        <span style="border: 2px dashed red;">2</span>
        <span style="border: 2px dashed red;">17</span>
        <span>36</span>
        <span>78</span>
        <span>84</span>
    </div>
    <div>
        <span>2</span>
        <span style="border: 2px dashed red;">17</span>
        <span style="border: 2px dashed red;">36</span>
        <span>78</span>
        <span>84</span>
    </div>
    <div>
        <span>2</span>
        <span>17</span>
        <span style="border: 2px dashed red;">36</span>
        <span style="border: 2px dashed red;">78</span>
        <span>84</span>
    </div>
    <div>
        <span>2</span>
        <span>17</span>
        <span>36</span>
        <span style="border: 2px dashed red;">78</span>
        <span style="border: 2px dashed red;">84</span>
    </div>
    <div style="border: 4px solid darkgreen;">
        <span>2</span>
        <span>17</span>
        <span>36</span>
        <span>78</span>
        <span>84</span>
    </div>
</div>

Your browser information:

User Agent is: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36

Challenge Information:

Build a Sorting Visualizer - Build a Sorting Visualizer

You should not be using a specific test array in your submitted code

That was just to confirm my program runs the same as the example project with the same input. If I uncomment fillArrContainer(startingArray, generateArray()); (and the testArr), my code is tested correctly with every step passing except 19.

Ok, makes sense. It helps to post the version you’re submitting for clarity

Yeah sorry should have mentioned. Here is a version of the submitted code and DOM output:

script.js
const genBtn = document.getElementById('generate-btn');
const sortBtn = document.getElementById('sort-btn');
const startingArray = document.getElementById('starting-array');
const arrayContainer = document.getElementById('array-container');

let count = 0;

// Return a random number between 1-100
const generateElement = () => {
    return Math.floor(Math.random() * 100 + 1);
}

// Return an array with 5 random numbers
const generateArray = () => {
    return Array.from({ length: 5 }, () => generateElement());
}

// Create and return an empty div
const generateContainer = () => {
    return document.createElement('div');
}

// Fill an HTML element with provided array values
const fillArrContainer = (container, arr) => {
    container.innerHTML = '';

    for (const num of arr) {
        const span = document.createElement('span');
        span.textContent = num;
        container.appendChild(span);
    }
}

// Return a boolean indicating if the first integer is 
// less than or equal to the second
const isOrdered = (a, b) => a <= b;

// Swap two sequential elements in an array if the first is larger than the second
const swapElements = (arr, i) => {
    if (!isOrdered(arr[i], arr[i + 1])) {
        const temp = arr[i];
        arr[i] = arr[i + 1];
        arr[i + 1] = temp;
    }
}

// Set a border on two elements being compared
const highlightCurrentEls = (el, i) => {
    const children = Array.from(el.children);

    children[i].style.border = "dashed 2px red";
    children[i + 1].style.border = "dashed 2px red";
}

// Recursively sort an array
const bubbleSortStep = (arr, i = 0, swapped = false) => {
    if (i >= arr.length - 1) {
        // Base case, array sorted
        if (!swapped) {
            return;
        }

        // Otherwise start new pass
        bubbleSortStep(arr, 0, false);
        return;
    }

    // List of array div elements currently within #array-container
    const divList = Array.from(arrayContainer.children);
    const arrList = [];

    // Get the most recent iteration (div array of span elements)
    // Extract values of span elements within in divs to array
    for (const div of divList) {
        const arrVal = Array.from(div.children).map(span => Number(span.textContent));
        arrList.push(arrVal);
    }

    // Highlight current comparison pair
    highlightCurrentEls(divList[count], i);

    // Swap elements if applicable
    if (!isOrdered(arrList[count][i], arrList[count][i + 1])) {
        swapElements(arrList[count], i);
        swapped = true;
    }

    // Fill new container with swapped array values and add to list of iterations
    const nextContainer = generateContainer();
    fillArrContainer(nextContainer, arrList[count]);
    arrayContainer.appendChild(nextContainer);

    // Move on to next comparison pair
    bubbleSortStep(arrList[count++], i + 1, swapped);
    return;
}

// Adds a green border to the final, sorted array div element
const highlightFinalArray = () => {
    const finalArray = Array.from(arrayContainer.children).pop();
    finalArray.style.border = "4px solid darkgreen";
}

genBtn.addEventListener('click', () => {
    count = 0;

    const arrayList = Array.from(arrayContainer.children);
    startingArray.innerHTML = "";

    // Reset #array-container if sorting has already occurred
    if (arrayList.length > 1) {
        arrayContainer.innerHTML = "";
        const newStartingArray = generateContainer();
        newStartingArray.id = 'starting-array';
        arrayContainer.append(startingArray);
        fillArrContainer(startingArray, generateArray());
    }

    fillArrContainer(startingArray, generateArray());
});

sortBtn.addEventListener('click', () => {
    const arr = Array.from(startingArray.children).map(el => Number(el.textContent));

    bubbleSortStep(arr);
    highlightFinalArray();
});
DOM output
<div id="array-container">
    <div id="starting-array">
        <span style="border: 2px dashed red;">88</span>
        <span style="border: 2px dashed red;">4</span>
        <span>96</span>
        <span>62</span>
        <span>86</span>
    </div>
    <div>
        <span>4</span>
        <span style="border: 2px dashed red;">88</span>
        <span style="border: 2px dashed red;">96</span>
        <span>62</span>
        <span>86</span>
    </div>
    <div>
        <span>4</span>
        <span>88</span>
        <span style="border: 2px dashed red;">96</span>
        <span style="border: 2px dashed red;">62</span>
        <span>86</span>
    </div>
    <div>
        <span>4</span>
        <span>88</span>
        <span>62</span>
        <span style="border: 2px dashed red;">96</span>
        <span style="border: 2px dashed red;">86</span>
    </div>
    <div>
        <span style="border: 2px dashed red;">4</span>
        <span style="border: 2px dashed red;">88</span>
        <span>62</span>
        <span>86</span>
        <span>96</span>
    </div>
    <div>
        <span>4</span>
        <span style="border: 2px dashed red;">88</span>
        <span style="border: 2px dashed red;">62</span>
        <span>86</span>
        <span>96</span>
    </div>
    <div>
        <span>4</span>
        <span>62</span>
        <span style="border: 2px dashed red;">88</span>
        <span style="border: 2px dashed red;">86</span>
        <span>96</span>
    </div>
    <div>
        <span>4</span>
        <span>62</span>
        <span>86</span>
        <span style="border: 2px dashed red;">88</span>
        <span style="border: 2px dashed red;">96</span>
    </div>
    <div>
        <span style="border: 2px dashed red;">4</span>
        <span style="border: 2px dashed red;">62</span>
        <span>86</span>
        <span>88</span>
        <span>96</span>
    </div>
    <div>
        <span>4</span>
        <span style="border: 2px dashed red;">62</span>
        <span style="border: 2px dashed red;">86</span>
        <span>88</span>
        <span>96</span>
    </div>
    <div>
        <span>4</span>
        <span>62</span>
        <span style="border: 2px dashed red;">86</span>
        <span style="border: 2px dashed red;">88</span>
        <span>96</span>
    </div>
    <div>
        <span>4</span>
        <span>62</span>
        <span>86</span>
        <span style="border: 2px dashed red;">88</span>
        <span style="border: 2px dashed red;">96</span>
    </div>
    <div style="border: 4px solid darkgreen;">
        <span>4</span>
        <span>62</span>
        <span>86</span>
        <span>88</span>
        <span>96</span>
    </div>
</div>

As far as I can tell, each div in #array-container only contains the five required span elements, with each having a number as its text, and seems to represent the steps in the bubble sort correctly, but I’m probably just missing something obvious.

I’m having a heck of a time getting my browser tools to work as expected, but I’m wondering what happens if you click generate and sort multiple times back to back. Then what’s your generated HTML?

Some odd things I noticed

  1. If I click “Sort Array” twice, more rows get added even though the array is already sorted

  2. I can get some undefined errors if I test your code many times

TypeError: container is undefined
TypeError: childen[i] is undefined

I appreciate you trying! I’ve set it to clear #array-container if it has more than one child (ie #starting-array) when generate is clicked.

I noticed the error of being able to add more rows by clicking sort twice and have now hidden the sort button after generate has been clicked, making it impossible to add more rows. Maybe it’s worth modifying the sortBtn event listener to prevent additional rows being added that way as well, though?

output

Errors are helpful! Did you get those just by clicking the sort button a lot?

I think just hiding the button won’t change whatever the test suite is doing since the test suite manually interacts with the DOM.

Yea, I got those errors by clicking the generate and sort buttons a bunch. The basic functionality seems all fine so I’m trying to see what I can get to break and where to get insight into what the tests see.

Much appreciated. I’ll have to leave it for now but I’ll try correcting the weird sort button functionality tomorrow and report back.

1 Like

I gave it a crack yesterday but unfortunately couldn’t figure it out.

I tried simply returning the sortBtn callback function early if a sort has already been performed:

sortBtn.addEventListener('click', () => {
    count = 0;
    const arr = Array.from(startingArray.children).map(span => Number(span.textContent));
    const arrayList = Array.from(arrayContainer.children);

    if (arrayList.length > 1) return;

    bubbleSortStep(arr);
    highlightFinalArray();
});

But this only caused step 20 to also fail now:

Failed: 19. After you click #sort-btn, each div within #array-container should contain five span, each with a number as its text, and arranged to represent the steps required by Bubble Sort algorithm to sort the starting array.
Failed: 20. When you click the #sort-btn, you should make use of the highlightCurrentEls function to highlight the elements being compared in each step.

I imagine it’s possibly failing because it expects highlightCurrentEls() to run each time the sort button is pressed, which won’t happen if a sort has already been performed with my above code.

So I tried clearing the list of sorted steps each time the sort button is pressed and simply running the sort again:

sortBtn.addEventListener('click', () => {
    count = 0;
    const arr = Array.from(startingArray.children).map(span => Number(span.textContent));
    const arrayList = Array.from(arrayContainer.children);

    if (arrayList.length > 1) {
        const newStartingArray = generateContainer();

        arrayContainer.innerHTML = "";
        newStartingArray.id = 'starting-array';
        arrayContainer.append(startingArray);
        fillArrContainer(startingArray, arr);
    }

    bubbleSortStep(arr);
    highlightFinalArray();
});

This fixes step 20 failing but doesn’t help with 19 sadly. I was able to get these pseudo error messages from the freeCodeCamp console (not dev tools) by spamming the generate button:

Potential infinite loop detected on line 10. Tests may fail if this is not changed.
Potential infinite loop detected on line 25. Tests may fail if this is not changed.

Line 10 is the function definition for generateElement() and 25 is fillArrContainer() so I’m not really sure how an infinite loop is being detected. I was thinking it may be my recursive sort bubbleSortStep() but that isn’t triggered by the generate button.