Build a Sorting Visualizer - Build a Sorting Visualizer

Tell us what’s happening:

I’ve seen the screenshot from staff of what the finished project should look like, and I’m stuck — the sorting algorithm works, but I can’t get startingArray with its first two elements highlighted to be the first div in arrayContainer. I’ve tried generating the container and filling it at a couple of different places followed by arrayContainer.appendChild at several places but can’t crack it. Is there a hint that could help me?

Your code so far

<!-- file: index.html -->

/* file: styles.css */

/* file: script.js */
const generateBtn = document.getElementById("generate-btn");
const sortBtn = document.getElementById("sort-btn");
const startingArray = document.getElementById("starting-array");
const arrayContainer = document.getElementById("array-container");

function generateElement() {
    return Math.floor(Math.random() * 100) + 1;
}

function generateArray() {
    const array = [];
    for (let i = 0; i < 5; i++) {
        array.push(generateElement());
    }
    console.log('Generated Array:', array);
    return array;
}

function generateContainer() {
    return document.createElement('div');
}

function fillArrContainer(element, array) {
    while (element.firstChild) {
        element.removeChild(element.firstChild);
    }
    array.forEach(item => {
        const span = document.createElement('span');
        span.textContent = item;
        element.appendChild(span);
    });
}

function isOrdered(n, m) {
    return (n <= m);
}

function swapElements(array, index) {
    if (index < 0 || index >= array.length - 1) {
        throw new Error('Index out of bounds');
    }

    if (!isOrdered(array[index], array[index + 1])) {
        [array[index], array[index + 1]] = [array[index + 1], array[index]];
    }
}

function highlightCurrentEls(parent, index) {
    const child1 = parent.children[index];
    const nextChild = parent.children[index + 1];
    if (child1) child1.style.border = "dashed red 4px";
    if (nextChild) nextChild.style.border = "dashed red 4px";
}

function resetHighlights(parent) {
    Array.from(parent.children).forEach(child => {
        child.style.border = "";
    });
}

function clearSnapshots() {
    Array.from(arrayContainer.children).forEach(child => {
        if (child.id !== 'starting-array') child.remove();
    });
}

async function bubbleSort(array) {
    const n = array.length;
    // Highlight startingArray
    resetHighlights(startingArray);
    highlightCurrentEls(startingArray, 0);

    for (let i = 0; i < n - 1; i++) {
        let swapped = false;

        for (let j = 0; j < n - 1; j++) {
            // Clear previous highlights
            resetHighlights(startingArray);
            // SNAPSHOT BEFORE SWAP: Create and highlight.
            const stepContainer = generateContainer();
            fillArrContainer(stepContainer, [...array]);
            highlightCurrentEls(stepContainer, j);
            arrayContainer.appendChild(stepContainer);

            // Delay to allow the user to see the highlights
            await new Promise((resolve) => setTimeout(resolve, 100));

            if (!isOrdered(array[j], array[j + 1])) {
                [array[j], array[j + 1]] = [array[j + 1], array[j]];
                swapped = true;
            }
        }

        if (!swapped) {
            break;
        }
    }

    const finalContainer = generateContainer();
    fillArrContainer(finalContainer, [...array]);
    finalContainer.style.border = "5px solid green";
    arrayContainer.appendChild(finalContainer);
    
    resetHighlights(startingArray);
}

generateBtn.addEventListener("click", () => {
    clearSnapshots();
    const array = generateArray();
    fillArrContainer(startingArray, array);
});

sortBtn.addEventListener("click", async () => {    clearSnapshots();
    clearSnapshots();
    const array = Array.from(startingArray.children).map(child => parseInt(child.textContent));
    await bubbleSort(array);
});

Your browser information:

User Agent is: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.2 Safari/605.1.15 Ddg/26.2

Challenge Information:

Build a Sorting Visualizer - Build a Sorting Visualizer

From what I can see, currently there’s few places where highlights of the starting array are specifically cleared.

Regarding duplicated step - notice that starting array already has element, while script creates additional elements each time.

One more thing that might give you troubles is adding delay while showing how array is sorted. Test might not expect this, and making its checks before final output is shown.

I removed the async/await statements, and the sort function/screen rendering works to create the final state ok. But arrayContainer still has a copy of startingArray without the red highlights as its first element. I’ve tried every combination I can think of of resetting and setting the highlights, force-creating a first container with startingArray, and different timing of arrayContainer.appendChild, and I can’t crack it. What else can I try?

const generateBtn = document.getElementById("generate-btn");
const sortBtn = document.getElementById("sort-btn");
const startingArray = document.getElementById("starting-array");
const arrayContainer = document.getElementById("array-container");

function generateElement() {
    return Math.floor(Math.random() * 100) + 1;
}

function generateArray() {
    const array = [];
    for (let i = 0; i < 5; i++) {
        array.push(generateElement());
    }
    console.log('Generated Array:', array);
    return array;
}

function generateContainer() {
    return document.createElement('div');
}

function fillArrContainer(element, array) {
    while (element.firstChild) {
        element.removeChild(element.firstChild);
    }
    array.forEach(item => {
        const span = document.createElement('span');
        span.textContent = item;
        element.appendChild(span);
    });
}

function isOrdered(n, m) {
    return (n <= m);
}

function swapElements(array, index) {
    if (index < 0 || index >= array.length - 1) {
        throw new Error('Index out of bounds');
    }

    if (!isOrdered(array[index], array[index + 1])) {
        [array[index], array[index + 1]] = [array[index + 1], array[index]];
    }
}

function highlightCurrentEls(parent, index) {
    const child1 = parent.children[index];
    const nextChild = parent.children[index + 1];
    if (child1) child1.style.border = "dashed red 4px";
    if (nextChild) nextChild.style.border = "dashed red 4px";
}

function resetHighlights(parent) {
    Array.from(parent.children).forEach(child => {
        child.style.border = "";
    });
}

function clearSnapshots() {
    Array.from(arrayContainer.children).forEach(child => {
        if (child.id !== 'starting-array') child.remove();
    });
}

function bubbleSort(array) {
    const n = array.length;

    // Highlight startingArray
    resetHighlights(startingArray);
    highlightCurrentEls(startingArray, 0);

    for (let i = 0; i < n - 1; i++) {
        let swapped = false;

        for (let j = 0; j < n - 1; j++) {
            resetHighlights(startingArray);
            highlightCurrentEls(startingArray, j);

            const stepContainer = generateContainer();
            fillArrContainer(stepContainer, [...array]);
            highlightCurrentEls(stepContainer, j);
            arrayContainer.appendChild(stepContainer);

            if (!isOrdered(array[j], array[j + 1])) {
                swapElements(array, j);
                swapped = true;
            }
//             // Delay to allow the user to see the highlights
// //            await new Promise((resolve) => setTimeout(resolve, 100));
        }

        if (!swapped) {
            break;
        }
    }

    const finalContainer = generateContainer();
    fillArrContainer(finalContainer, [...array]);
    finalContainer.style.border = "5px solid green";
    arrayContainer.appendChild(finalContainer);
    
    resetHighlights(startingArray);
}

generateBtn.addEventListener("click", () => {
    clearSnapshots();
    const array = generateArray();
    fillArrContainer(startingArray, array);
});

sortBtn.addEventListener("click", () => {    clearSnapshots();
    clearSnapshots();
    const array = Array.from(startingArray.children).map(child => parseInt(child.textContent));
    bubbleSort(array);
});

Thank you for any guidance you can provide.

Why are you doing this?

I thought it was necessary to clear the prior highlights before applying the next set, but I commented out the first two and it made no difference in behavior. If the third one is commented out, the display finishes with all of the elements in the first array highlighted.

function bubbleSort(array) {
    const n = array.length;

    // Highlight startingArray
//    resetHighlights(startingArray);
    highlightCurrentEls(startingArray, 0);

    for (let i = 0; i < n - 1; i++) {
        let swapped = false;

        for (let j = 0; j < n - 1; j++) {
//            resetHighlights(startingArray);
            highlightCurrentEls(startingArray, j);

            const stepContainer = generateContainer();
            fillArrContainer(stepContainer, [...array]);
            highlightCurrentEls(stepContainer, j);
            arrayContainer.appendChild(stepContainer);

            if (!isOrdered(array[j], array[j + 1])) {
                swapElements(array, j);
                swapped = true;
            }
//             // Delay to allow the user to see the highlights
// //            await new Promise((resolve) => setTimeout(resolve, 100));
        }

        if (!swapped) {
            break;
        }
    }

    const finalContainer = generateContainer();
    fillArrContainer(finalContainer, [...array]);
    finalContainer.style.border = "5px solid green";
    arrayContainer.appendChild(finalContainer);
    
    resetHighlights(startingArray);
}

the startingArray div should be part of the bubble sort to sort the first pair of numbers if necessary. but it does not need to be generated, since it already has been. you don’t need resetHighlights.

OK — I to rid of resetHighlights, and everything’s ok. Then I added a conditional to the nested loop so that j starts at 1 the first time through and 0 all the others, and now I get the first array in arrayContainer correct. And it passes the test for the right number of steps. Now it’s only failing test 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.

and in the console I have this error, but I don’t know what it’s testing:

[Error] n

actual: 26

expected: 9

message: "expected 26 to equal 9"

operator: "strictEqual"

showDiff: true

stack: "@↵forEach@[native code]↵@↵forEach@[native code]↵@↵@↵eval code@↵eval@[native code…”

Is the test passing a 9-element array? I modified my generateArray() function for a 9-element array and it took (correctly, I think) more than 26, certainly more than 9, steps.

n Prototype

(anonymous function) (dom-test-evaluator.js:2:256860)

Current state of my code without changing it to a 9-element array:

const generateBtn = document.getElementById("generate-btn");
const sortBtn = document.getElementById("sort-btn");
const startingArray = document.getElementById("starting-array");
const arrayContainer = document.getElementById("array-container");

function generateElement() {
    return Math.floor(Math.random() * 100) + 1;
}

function generateArray() {
    const array = [];
    for (let i = 0; i < 5; i++) {
        array.push(generateElement());
    }
    console.log('Generated Array:', array);
    return array;
}

function generateContainer() {
    return document.createElement('div');
}

function fillArrContainer(element, array) {
    while (element.firstChild) {
        element.removeChild(element.firstChild);
    }
    array.forEach(item => {
        const span = document.createElement('span');
        span.textContent = item;
        element.appendChild(span);
    });
}

function isOrdered(n, m) {
    return (n <= m);
}

function swapElements(array, index) {
    if (index < 0 || index >= array.length - 1) {
        throw new Error('Index out of bounds');
    }

    if (!isOrdered(array[index], array[index + 1])) {
        [array[index], array[index + 1]] = [array[index + 1], array[index]];
    }
}

function highlightCurrentEls(parent, index) {
    const child1 = parent.children[index];
    const nextChild = parent.children[index + 1];
    if (child1) child1.style.border = "dashed red 4px";
    if (nextChild) nextChild.style.border = "dashed red 4px";
}

function resetHighlights(parent) {
    Array.from(parent.children).forEach(child => {
        child.style.border = "";
    });
}

function clearSnapshots() {
    Array.from(arrayContainer.children).forEach(child => {
        if (child.id !== 'starting-array') child.remove();
    });
}

function bubbleSort(array) {
    const n = array.length;

    for (let i = 0; i < n - 1; i++) {
        let swapped = false;
        let j;
        if (i == 0) {j = 1} else {j = 0}
        for (; j < n - 1; j++) {

            const stepContainer = generateContainer();
            fillArrContainer(stepContainer, [...array]);
            highlightCurrentEls(stepContainer, j);
            arrayContainer.appendChild(stepContainer);

            if (!isOrdered(array[j], array[j + 1])) {
                swapElements(array, j);
                swapped = true;
            }
        }
        if (!swapped) {
            break;
        }
    }

    const finalContainer = generateContainer();
    fillArrContainer(finalContainer, [...array]);
    finalContainer.style.border = "5px solid green";
    arrayContainer.appendChild(finalContainer);
    
//    resetHighlights(startingArray);
}

generateBtn.addEventListener("click", () => {
    clearSnapshots();
    const array = generateArray();
    fillArrContainer(startingArray, array);
    highlightCurrentEls(startingArray, 0);
});

sortBtn.addEventListener("click", () => {    clearSnapshots();
    clearSnapshots();
    const array = Array.from(startingArray.children).map(child => parseInt(child.textContent));
    bubbleSort(array);
});

The first two elements in your starting array are not being sorted:

I just realized that, I’m working on it now.

I got it. I needed a condition to suppress duplicating the starting array being appended to arrayContainer while still sorting it. Yikes that was a challenge!

1 Like