diff --git a/Dockerfile b/Dockerfile index c97b2e15e5ba44d2403bdf25ca9730e6fc86ce40..2e7e291443f1e8a9b36351cea594bee4f242b08f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ COPY vite.config.js . # port 5173 EXPOSE 5173 +EXPOSE 9229 #Start -CMD ["npm", "run", "dev", "--", "--host"] +CMD ["npm", "run", "dev", "--", "--host", "$DEBUG_FLAG"] diff --git a/docker-compose.yml b/docker-compose.yml index ba7c89f7dc7d7ca4916cfc762293853f7bc9316c..fe4480d9da5d14281de4debd336037d19b2775aa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,13 +4,16 @@ services: container_name: Sorting-Visualizer ports: - 127.0.0.1:5173:5173 + - 127.0.0.1:9229:9229 + volumes: - ./src/:/app/sorting-visualizer/src/ - ./package-lock.json:/app/sorting-visualizer/package-lock.json - ./package.json:/app/sorting-visualizer/package.json networks: - my-network - + environment: + DEBUG_FLAG: "--inspect=0.0.0.0:9229" networks: my-network: driver: bridge diff --git a/src/MergeSort.js b/src/MergeSort.js new file mode 100644 index 0000000000000000000000000000000000000000..2c935a13c5cbe316bafa045d90a08401c5c6873e --- /dev/null +++ b/src/MergeSort.js @@ -0,0 +1,122 @@ +async function MergeSort(EingangsArray, startIdx, endIdx, arrayCopy, updateBars, setColor, sortSpeed) { + if (startIdx >= endIdx) return; + + // Split array + const cuttingEdge = Math.floor((startIdx + endIdx) / 2); + + // Recursively sort both halves + await MergeSort(EingangsArray, startIdx, cuttingEdge, arrayCopy, updateBars, setColor, sortSpeed); + await MergeSort(EingangsArray, cuttingEdge + 1, endIdx, arrayCopy, updateBars, setColor, sortSpeed); + + // Merge halves back togeteher + await merge(EingangsArray, startIdx, cuttingEdge, endIdx, arrayCopy, updateBars, setColor, sortSpeed); +} + +async function merge(EingangsArray, startIdx, middleIdx, endIdx, arrayCopy, updateBars, setColor, sortSpeed) { + let i = startIdx; + let j = middleIdx + 1; + let k = startIdx; + const bars = document.querySelectorAll('.bar'); // get all bars + const sortSpeedSwitch = Number(sortSpeed); + + + // index exist? + const isValidIndex = (index) => index >= 0 && index < bars.length; + + // Merge halves back togeteher + while (i <= middleIdx && j <= endIdx) { + if (isValidIndex(i) && isValidIndex(j)) { + // Change color of compared bars + bars[i].style.backgroundColor = 'red'; + bars[j].style.backgroundColor = 'red'; + } + + switch (sortSpeedSwitch) { + case 0: + break; // No delay + case 40: + await new Promise(resolve => setTimeout(resolve, 90)); + break; + case 100: + await new Promise(resolve => setTimeout(resolve, 200)); + break; + } + if (EingangsArray[i] <= EingangsArray[j]) { + arrayCopy[k] = EingangsArray[i]; + i++; + } else { + arrayCopy[k] = EingangsArray[j]; + j++; + } + + // Update Array + await updateArrayState(EingangsArray, arrayCopy, startIdx, endIdx, updateBars, sortSpeed); + if (isValidIndex(i - 1)) bars[i - 1].style.backgroundColor = ''; // default colr + if (isValidIndex(j - 1)) bars[j - 1].style.backgroundColor = ''; + + k++; + } + + // Copy from the LEFT half + while (i <= middleIdx) { + switch (sortSpeedSwitch) { + case 0: + break; // No delay + case 40: + await new Promise(resolve => setTimeout(resolve, 90)); + break; + case 100: + await new Promise(resolve => setTimeout(resolve, 200)); + break; + } + + arrayCopy[k] = EingangsArray[i]; + await updateArrayState(EingangsArray, arrayCopy, startIdx, endIdx, updateBars, sortSpeed); + if (isValidIndex(i)) bars[i].style.backgroundColor = ''; + + i++; + k++; + } + + // Copy from the RIGHT half + while (j <= endIdx) { + switch (sortSpeedSwitch) { + case 0: + break; // No delay + case 40: + await new Promise(resolve => setTimeout(resolve, 90)); + break; + case 100: + await new Promise(resolve => setTimeout(resolve, 200)); + break; + } + + arrayCopy[k] = EingangsArray[j]; + await updateArrayState(EingangsArray, arrayCopy, startIdx, endIdx, updateBars, sortSpeed); + if (isValidIndex(j)) bars[j].style.backgroundColor = ''; + + j++; + k++; + } + + // Copy merged values back + for (let i = startIdx; i <= endIdx; i++) { + EingangsArray[i] = arrayCopy[i]; + } + + // adding to class 'finished' + if (endIdx - startIdx + 1 === EingangsArray.length) { + setColor(0); + } +} + +async function updateArrayState(EingangsArray, arrayCopy, startIdx, endIdx, updateBars, sortSpeed) { + updateBars([...arrayCopy]); + await new Promise(resolve => setTimeout(resolve, sortSpeed)); +} + +export default async function startMergeSort(EingangsArray, updateBars, setFinish, sortSpeed) { + const arrayCopy = [...EingangsArray]; + + await MergeSort(EingangsArray, 0, EingangsArray.length - 1, arrayCopy, updateBars, setFinish, sortSpeed); +} diff --git a/src/SortingVisualizer.css b/src/SortingVisualizer.css index 991362a5f34dea4254dd4ff840b1a15040e4b69c..91b92669ec2bf48990be410f3d4567f56a909d9b 100644 --- a/src/SortingVisualizer.css +++ b/src/SortingVisualizer.css @@ -15,6 +15,9 @@ body { height: 100px; flex: 1 1 20px; min-width: 2px; + margin: 0px; + transform-origin: bottom; + } .sorted { @@ -75,13 +78,20 @@ button:active { } .DivArray { - transform: scaleY(-1); display: flex; + position: absolute; + position: fixed; flex-wrap: nowrap; justify-content: center; gap: 2px; width: 90%; - margin-top: 5px; + margin-top: 60px; + align-items: flex-end; + overflow: hidden; + height: 400px; +/ + + } #dropdown-menu { @@ -91,7 +101,6 @@ button:active { margin-top: 3px; } - #dropdown-item { color: white; font-size: 16px; @@ -115,7 +124,6 @@ button:active { height: 100%; display: flex; place-items: center; - margin-inline: 10px; justify-content: center; width: 195px; transition: none; @@ -135,6 +143,17 @@ button:active { color: white; } +.Divider { + background: gray; + height: 70%; + margin-left: 8px; + margin-right: 3px; + width: 2px; +} + +#dividerLeft { + margin-right: 8px; +} @keyframes ScaleAnimation { 0% { diff --git a/src/SortingVisualizer.jsx b/src/SortingVisualizer.jsx index 4521e25615e08a8aa06448a084eacbfff01b354b..6fe070a3e55006c3298f8674735ee7d35ce72436 100644 --- a/src/SortingVisualizer.jsx +++ b/src/SortingVisualizer.jsx @@ -1,6 +1,7 @@ import {useState, useEffect} from 'react'; import './SortingVisualizer.css' import BubbleSort from "./BubbleSort"; +import startMergeSort from "./MergeSort.js"; import RangeSlider from 'react-bootstrap-range-slider'; import 'react-bootstrap-range-slider/dist/react-bootstrap-range-slider.css'; import {Dropdown} from 'react-bootstrap'; @@ -13,9 +14,10 @@ function SortingVisualizer() { const [BarNumber, setBarNumber] = useState(() => monitorSize > 100 ? 100 : monitorSize); // max of 100 bars const [bars, setbars] = useState(() => generateArray(BarNumber)); const [sorting, setSorting] = useState(false); - const [alreadySorted, setAlreadySorted] = useState(); + const [alreadySorted, setColor] = useState(); const [isSorted, setIsSorted] = useState(false); const [sortSpeed, setSortSpeed] = useState('40'); + const [Algo, setAlgo] = useState('BubbleSort'); const SliderWithInputFormControl = () => { const [sliderValue_intern, setSliderValue_intern] = useState(BarNumber); @@ -41,18 +43,22 @@ function SortingVisualizer() { } function generateArray(numberBars) { - return Array.from({length: numberBars}, () => getRandomArbitrary(50, 300)); + return Array.from({length: numberBars}, () => getRandomArbitrary(20, 400)); } async function handleSorting() { if (sorting) return; setSorting(true); - await BubbleSort(bars, setbars, setAlreadySorted, sortSpeed); + if (Algo == 'BubbleSort') { + await BubbleSort(bars, setbars, setColor, sortSpeed); + } else if (Algo == 'MergeSort') { + await startMergeSort(bars, setbars, setColor, sortSpeed); + } setSorting(false); } function resetBars() { - setAlreadySorted(1000); + setColor(1000); setbars(generateArray(BarNumber)); let ColoredBars = document.getElementsByClassName('sorted'); document.getElementById('startButton').innerText = 'Sort!'; @@ -73,7 +79,7 @@ function SortingVisualizer() { function handleButton() { if (!sorting && !isSorted) { - setAlreadySorted(1000); + setColor(1000); handleSorting(); } else if (isSorted) { resetBars(); @@ -106,14 +112,15 @@ function SortingVisualizer() { value={BarNumber} style={{ width: '42px', fontfamily: 'Arial sans-serif', - fontSize:'16px', - margin: '10px', + fontSize: '16px', + marginLeft: '10px', + marginRight: '5px', borderRadius: '5px', border: 'none', textAlign: 'center' }}/> - <div style={{background: 'gray', height: '70%', marginLeft: '10px', width: '2px'}}></div> - <Dropdown id="dropdown"> + <div id="dividerLeft" className="Divider"></div> + <Dropdown id="dropdown" className="dropdown-wide"> <Dropdown.Toggle id="dropdown" disabled={sorting} @@ -138,6 +145,26 @@ function SortingVisualizer() { </Dropdown.Item> </Dropdown.Menu> </Dropdown> + <div className="Divider"></div> + <Dropdown id="dropdown"> + <Dropdown.Toggle + id="dropdown" + disabled={sorting} + > + Algorithm: { + [Algo] + } + </Dropdown.Toggle> + <Dropdown.Menu id="dropdown-menu"> + <Dropdown.Item id="dropdown-item" onClick={() => setAlgo('BubbleSort')}> + BubbleSort + </Dropdown.Item> + <Dropdown.Item id="dropdown-item" onClick={() => setAlgo('MergeSort')}> + MergeSort + </Dropdown.Item> + </Dropdown.Menu> + </Dropdown> + <button id='startButton' className="Button" disabled={sorting} style={{background: `${sorting ? 'red' : ''}`}} onClick={() => { handleButton()