Build a Sorting Visualizer - Build a Sorting Visualizer - Step 18

Tell us what’s happening:

My implementation of bubble sort seems to give the correct sorted result but in comparison to the example it has one div too many, the first one with blue borders should have the starting array and then come the steps of the bubble sort, but it seems like in the example it starts highlighting the numbers one index to the right whereas my version starts from the first index so it ends with one too many steps.

Is this what is called an off-by-one error? I’ve been doing this test for 2 days and I am out of ideas, it seems to be close but I’m not sure where to tweak it as adding 1 to the j in highlightCurrentEls(div, j) would result in out of bounds error when comparing the second to last with the last element of the array.

Your code so far

<!-- file: index.html -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Sorting Visualizer</title>
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <main>
        <div id="array-container">
            <div id="starting-array"></div>
        </div>
        <div id="btn-container">
            <button id="generate-btn" type="button">Generate Array</button>
            <button id="sort-btn" type="button">Sort Array</button>
        </div>
    </main>
    <script src="script.js"></script>
</body>

</html>

/* file: styles.css */
* {
    box-sizing: border-box;
}

main {
    height: 100vh;
    display: flex;
    justify-content: center;
    flex-direction: column;
    align-items: center;
}

#array-container {
    max-height: 95vh;
    display: flex;
    flex-direction: column;
    flex-wrap: wrap;
    gap: 2px;

}

#array-container>div {
    min-width: 8rem;
    height: 2rem;
    box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
    border-radius: 10px;
    margin-bottom: 0.2rem;
    border: 2px solid darkgray;
    display: flex;
    justify-content: space-evenly;
    align-items: center;
}

#starting-array {
    border: 4px solid darkblue !important;
}

#btn-container {
    display: flex;
    justify-content: space-around;
}

button {
    padding: 2px;
    margin: 5px;
}

span {
    border-radius: 2px;
    padding: 0.5px;
    margin: 0
}

@media (min-width: 430px) {
  #array-container>div {
    min-width: 12rem;    
  }
  span {
    padding: 1px;
    margin: 1px;
  }
}

/* file: script.js */
const arrContainer = document.getElementById('array-container');
const startingArr = document.getElementById('starting-array');

const generateBtn = document.getElementById('generate-btn');
const sortBtn = document.getElementById('sort-btn');

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

function generateArray() {
  const randNums = [];
  while (randNums.length <= 4) {
    const randInt = generateElement();
    randNums.push(randInt);
  }
  return randNums;
}

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

function fillArrContainer(element, arr) {
  for (const integer of arr) {
    let span = document.createElement('span');
    span.innerHTML = integer;
    span.setAttribute('id', `no-${integer}`)
    element.appendChild(span);
  }
  return element;
}

function isOrdered(num1, num2) {
  return num1 <= num2;
}

function swapElements(arr, index) {
  const currNum = arr[index];
  const nextNum = arr[index + 1];
  if (!isOrdered(currNum, nextNum)) {
    arr[index] = nextNum;
    arr[index+1] = currNum;
    return true;
  }
  return false;
}

function highlightCurrentEls(element, index) {
  element.children[`${index}`].style.border = '1px dashed red';
  element.children[`${index+1}`].style.border = '1px dashed red';
}

generateBtn.addEventListener('click', () => {
  //clear sorted divs
  const sortedDivs = arrContainer.querySelectorAll('.sorted-divs');
  if (sortedDivs.length) {
    Object.keys(sortedDivs).forEach((key) => {
      sortedDivs[key].remove();
    });
  }

  // fill startingArr with the generated array
  startingArr.innerHTML = '';
  const arr = generateArray();
  const container = generateContainer();
  const containedSpan = fillArrContainer(startingArr, arr);
})

sortBtn.addEventListener('click', () => {
  // get array from the span elements
  const spanEls = startingArr.children
  let arr = Object.keys(spanEls).map((node) => {
    return Number(spanEls[node].textContent)
  });
  arr = [85, 25, 64, 67, 79];
  // highlighting startingArr
  startingArr.innerHTML = '';
  fillArrContainer(startingArr, arr);
  highlightCurrentEls(startingArr, 0);

  // nested loops for bubble sort
  let swapped;
  for (let i = 0; i < arr.length; i++) {
    swapped = false;
    for (let j = 0; j < (arr.length - 1); j++) {
      if (swapElements(arr, j)) {
        swapped = true;
      }
      const div = generateContainer();
      fillArrContainer(div, arr);
      highlightCurrentEls(div, j);
      div.classList.add('sorted-divs');
      arrContainer.appendChild(div);
    }
    if (!swapped) {
      break;
    }
  }

  // append sorted div
  const lastDiv = generateContainer();
  lastDiv.style.border = '2px solid green';
  lastDiv.classList.add('sorted-divs');
  fillArrContainer(lastDiv, arr);
  arrContainer.appendChild(lastDiv);
})

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:143.0) Gecko/20100101 Firefox/143.0

Challenge Information:

Build a Sorting Visualizer - Build a Sorting Visualizer

Try going step-by-step through the beginning of the sorting callback. Consider what happens when - when initial array is displayed, when further div elements are created, at which point numbers are highlighted?

I wouldn’t call it off-by-one error. There’s one more div, but that’s not caused by adding, or removing 1 to/from somewhere.

1 Like

Ok I’ve made changes by creating and highlighting the newly created div elements before calling the function to swap them, the result is the same as the example now but tests 18 and 19 still fail:
(example on the left — mine on the right)

Both steps 18 and 19 mention having divs representing the steps of the bubble sort algorithm so I suspect the way I set #starting-array out of the loop might be considering it a step missing even if the result looks the same.

const arrContainer = document.getElementById('array-container');
const startingArr = document.getElementById('starting-array');

const generateBtn = document.getElementById('generate-btn');
const sortBtn = document.getElementById('sort-btn');

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

function generateArray() {
  const randNums = [];
  while (randNums.length <= 4) {
    const randInt = generateElement();
    randNums.push(randInt);
  }
  return randNums;
}

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

function fillArrContainer(element, arr) {
  for (const integer of arr) {
    let span = document.createElement('span');
    span.innerHTML = integer;
    span.setAttribute('id', `no-${integer}`)
    element.appendChild(span);
  }
  return element;
}

function isOrdered(num1, num2) {
  return num1 <= num2;
}

function swapElements(arr, index) {
  const currNum = arr[index];
  const nextNum = arr[index + 1];
  if (!isOrdered(currNum, nextNum)) {
    arr[index] = nextNum;
    arr[index+1] = currNum;
    return true;
  }
  return false;
}

function highlightCurrentEls(element, index) {
  element.children[`${index}`].style.border = '2px dashed red';
  element.children[`${index+1}`].style.border = '2px dashed red';
}

generateBtn.addEventListener('click', () => {
  //clear sorted divs
  const sortedDivs = arrContainer.querySelectorAll('.sorted-divs');
  if (sortedDivs.length) {
    Object.keys(sortedDivs).forEach((key) => {
      sortedDivs[key].remove();
    });
  }

  // fill startingArr with the generated array
  startingArr.innerHTML = '';
  const arr = generateArray();
  const container = generateContainer();
  const containedSpan = fillArrContainer(startingArr, arr);
})

sortBtn.addEventListener('click', () => {
  // get array from the span elements
  const spanEls = startingArr.children
  let arr = Object.keys(spanEls).map((node) => {
    return Number(spanEls[node].textContent)
  });
  arr = [59, 13, 15, 87, 98]; // hardcoded array for testing

  // highlighting startingArr
  startingArr.innerHTML = '';
  fillArrContainer(startingArr, arr);
  highlightCurrentEls(startingArr, 0);

  // nested loops for bubble sort
  let swapped;
  for (let i = 0; i < arr.length; i++) {
    swapped = false;
    for (let j = 0; j < (arr.length - 1); j++) {
      const div = generateContainer();
      fillArrContainer(div, arr);
      highlightCurrentEls(div, j);
      
      if (swapElements(arr, j)) {
        swapped = true;
      }
      //const div = generateContainer();
      //fillArrContainer(div, arr);
      //highlightCurrentEls(div, j);
      div.classList.add('sorted-divs');
      if (!(div.innerHTML === startingArr.innerHTML)) {
      arrContainer.appendChild(div);}
    }
    if (!swapped) {
      break;
    }
  }

  // append sorted div
  const lastDiv = generateContainer();
  lastDiv.style.border = '4px solid green';
  lastDiv.classList.add('sorted-divs');
  fillArrContainer(lastDiv, arr);
  arrContainer.appendChild(lastDiv);
})

Are you removing the hardcoded array when running tests?

1 Like

Well that was awkward… It is fine now, thanks for the help!

1 Like