<h1>Radix Sort</h1>

<div id='buttonContainer'>
  <button id="randomize">Randomize</button>
  <button id="sort">Sort</button>
</div>

<div>
  <label for="slider">Delay in seconds</label>
  <input type="range" id="slider" value="1" min="0" max="3" step="0.5">
  <span id="seconds">1</span>
</div>

<div id="spanContainer">
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
</div>

<div id="resultsContainer">
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
</div>

<code>Best -- Time: O(nk) Space: O(n + k)</code>
<code>Ave -- Time: O(nk) Space: O(n + k)</code>
<code>Worst -- Time: O(nk) Space: O(n + k)</code>
* {
  font-family: Arial;
  font-size: 3.5vw;
  outline: none;
}

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

h1 {
  margin: 0 0 12.5vh;
  font-size: 6vw;
  font-weight: 400;
}

#buttonContainer {
  display: flex;
  justify-content: center;
}

button {
  width: 25vw;
  padding: 1vw;
  margin: 0.5vw 0.5vw 1.5vw;
  border: 1px solid black;
  border-radius: 25vw;
  background-color: white;
  cursor: pointer;
  transition: all 0.2s ease;
}

button:hover {
  background-color: black;
  color: white;
  box-shadow: 0 0 3.5vw gray;
}

#slider {
  margin-right: 10px;
}

#seconds {
  position: absolute;
}

#spanContainer {
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 12.5vh 0 3vh;
}

#resultsContainer {
  display: flex;
  justify-content: center;
  align-items: center; 
}

#spanContainer span, #resultsContainer span {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 3.5vw;
  height: 3.5vw;
  margin: 0 0.5vw;
  padding: 1.5vw;
  border: 1px solid black;
  border-radius: 3.5vw;
}

/* ----------------------------------------------
 * Generated by Animista on 2020-5-30 8:25:12
 * Licensed under FreeBSD License.
 * Edited by Kent Warren
 * See http://animista.net/license for more info. 
 * w: http://animista.net, t: @cssanimista
 * ---------------------------------------------- */

/**
 * ----------------------------------------
 * animation pulsate-fwd
 * ----------------------------------------
 */

.pulsate-fwd {
	-webkit-animation: pulsate-fwd 0.75s ease-in-out 3 both;
	        animation: pulsate-fwd 0.75s ease-in-out 3 both;
}

@-webkit-keyframes pulsate-fwd {
  0% {
    -webkit-transform: scale(1);
            transform: scale(1);
  }
  50% {
    -webkit-transform: scale(1.1);
            transform: scale(1.1);
           box-shadow: 0 0 1vw gray;
  }
  100% {
    -webkit-transform: scale(1);
            transform: scale(1);
  }
}

@keyframes pulsate-fwd {
  0% {
    -webkit-transform: scale(1);
            transform: scale(1);
  }
  50% {
    -webkit-transform: scale(1.1);
            transform: scale(1.1);
           box-shadow: 0 0 1vw gray;
  }
  100% {
    -webkit-transform: scale(1);
            transform: scale(1);
  }
}

code {
  font-family: monospace;
  margin: 3vh 0 0;
}

code:first-of-type {
  margin: 12.5vh 0 0;
}

sup {
  font-family: monospace;
}
console.clear()

// randomize button
const randomize = document.getElementById('randomize');
// sort button
const sort = document.getElementById('sort');
// delay input
const delay = document.getElementById('slider');
// delay value
let delayValue = delay.value * 1000;
// delay span
const delaySpan = document.getElementById('seconds');
// for when sort is in process
let sortInProcess = false;
// span container
const spanContainer = document.getElementById('spanContainer');
// span container children
const randomSpans = [...spanContainer.children];
// div of spans for results
const resultsContainer = document.getElementById('resultsContainer');



// randomize
randomize.addEventListener('click', () => {
  // if in progress, return
  if (sortInProcess) return;
  
  [...resultsContainer.children].forEach((span, i) => {
    span.innerText = '';
    span.style.color = 'black';
    span.className = '';
    randomSpans[i].innerText = Math.floor( Math.random() * 100 );
  });
});



// sort
sort.addEventListener('click', async () => {
  // if in progress or no random numbers yet or all green, return
  if (sortInProcess || randomSpans[0].innerText === '' || resultsContainer.children[0].style.color === 'green') return;
  sortInProcess = true;
  
  // clone nodes (true means deep clone)
  resultsContainer.innerHTML = '';
  const arr = randomSpans.map(span => {
    let cloned = span.cloneNode(true);
    resultsContainer.append(cloned);
    return cloned;
  });
  
  // start radix sort
  await radixSort(arr);  
  
  // revert state
  sortInProcess = false;
});



// delay
delay.addEventListener('input', (e) => {
  delaySpan.innerText = e.target.value;
  delayValue = e.target.value * 1000;
});



async function radixSort(arr) {
  let max = mostDigits(arr);

  for (let i = 0; i < max; i++) {
    let buckets = Array.from( {length: 10}, () => [] );

    for (let j = 0; j < arr.length; j++) {
      buckets[ getDigit( Number(arr[j].innerText), i ) ].push(arr[j]);
    }

    arr = buckets.flat();
    
    // delay for effect
    await wait();
    
    // update DOM
    updateDOM(arr);
  }

  // make resultsContainer spans green
  arr.forEach(span => {
    span.style.color = 'green';
    span.className = 'pulsate-fwd';
  });
  
  return arr;
}

const getDigit = (num, pos) => {
  return Math.floor( Math.abs(num) / Math.pow(10, pos) ) % 10;
};

const digitCount = num => {
  if (num === 0) return 1;
  return Math.floor( Math.log10( Math.abs(num) ) ) + 1;
};

const mostDigits = arr => {
  let max = 0;
  for (let i = 0; i < arr.length; i++) {
    max = Math.max(max, digitCount( Number(arr[i].innerText) ));
  }
  return max;
};

const wait = () => {
  return new Promise(resolve => {
    setTimeout(() => resolve(), delayValue);
  });
};

const updateDOM = arr => {
  resultsContainer.innerHTML = '';
  arr.forEach(span => resultsContainer.append(span));
};



/*

Unadulterated radix sort


const getDigit = (num, pos) => {
  return Math.floor( Math.abs(num) / Math.pow(10, pos) ) % 10;
};

const digitCount = num => {
  if (num === 0) return 1;
  return Math.floor( Math.log10( Math.abs(num) ) ) + 1;
};

const mostDigits = arr => {
  let max = 0;
  for (let i = 0; i < arr.length; i++) {
    max = Math.max(max, digitCount(arr[i]));
  }
  return max;
};

function radixSort(arr) {
  let max = mostDigits(arr);

  for (let i = 0; i < max; i++) {
    let buckets = Array.from( {length: 10}, () => [] );

    for (let j = 0; j < arr.length; j++) {
      buckets[ getDigit(arr[j], i) ].push(arr[j]);
    }

    arr = buckets.flat();
    // alternative: arr = [].concat(...buckets);
  }

  return arr;
}

*/

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.