Visualizing Sorting algorithm 
from visualizer import SortVisualizer

class BubbleSort(SortVisualizer):
    """
    BubbleSort class that extends the SortVisualizer class to visualize the bubble sort algorithm.

    Attributes:
        data (list): A list of integers to be sorted.
    """

    def __init__(self, data):
        """
        Constructor for the BubbleSort class.

        Args:
            data (list): A list of integers to be sorted.
        """
        super().__init__(data)

    def sort(self):
        """
        Implements the bubble sort algorithm to sort the data in ascending order.
        During each pass, it compares adjacent elements and swaps them if they are in the wrong order.
        The process is repeated for each element in the list, ensuring the largest element bubbles up to its correct position.
        The visualization is updated after each swap or comparison.
        """
        for i in range(len(self.data) - 1):  # Outer loop for each pass
            for j in range(len(self.data) - 1 - i):  # Inner loop for comparing adjacent elements
                if self.data[j] > self.data[j + 1]:  # If the current element is greater than the next element
                    # Swap the elements
                    self.data[j], self.data[j + 1] = self.data[j + 1], self.data[j]
                    # Update the visualization with the swapped indices
                    self.update(j, j+1)
                else:
                    # Update the visualization without swapping
                    self.update()

algorithms/A_BubbleSort/sort.py
WasinUddy/PySort
from visualizer import SortVisualizer

class SelectionSort(SortVisualizer):
    """
    SelectionSort class that extends the SortVisualizer class to visualize the selection sort algorithm.

    Attributes:
        data (list): A list of integers to be sorted.
    """

    def __init__(self, data):
        """
        Constructor for the SelectionSort class.

        Args:
            data (list): A list of integers to be sorted.
        """
        super().__init__(data)

    def sort(self):
        """
        Implements the selection sort algorithm to sort the data in ascending order.
        During each pass, it identifies the smallest (or largest, depending on sorting order) 
        element from the unsorted segment of the list and swaps it with the first unsorted element.
        The visualization is updated after each comparison and swap.

        The algorithm divides the input list into two parts:
        - A sorted sublist which is built up from left to right at the front (left) of the list.
        - A sublist of the remaining unsorted items.
        """
        for i in range(len(self.data)):  # Outer loop to move the boundary of the unsorted segment
            min_idx = i  # Assume the minimum value is at the current index
            for j in range(i+1, len(self.data)):  # Inner loop to iterate over the unsorted segment
                if self.data[j] < self.data[min_idx]:  # If a smaller value is found
                    min_idx = j  # Update the minimum index
                self.update()  # Update the visualization after each comparison
            # Swap the found minimum element with the first element of the unsorted segment
            self.data[i], self.data[min_idx] = self.data[min_idx], self.data[i]
            # Update the visualization after the swap
            self.update(i, min_idx)

algorithms/B_SelectionSort/sort.py
WasinUddy/PySort
from visualizer import SortVisualizer

class InsertionSort(SortVisualizer):
    """
    InsertionSort class that extends the SortVisualizer class to visualize the insertion sort algorithm.

    Attributes:
        data (list): A list of integers or comparable elements to be sorted.
    """

    def __init__(self, data):
        """
        Constructor for the InsertionSort class.

        Args:
            data (list): A list of integers or comparable elements to be sorted.
        """
        super().__init__(data)

    def sort(self):
        """
        Implements the insertion sort algorithm to sort the data in ascending order.
        
        The algorithm works by building a sorted portion of the list one element at a time. 
        For each position, it checks the current element against the previous elements in the sorted portion.
        If the current element is smaller, it shifts the larger elements to the right until the correct position 
        for the current element is found. The current element is then inserted in its correct position.
        
        The visualization is updated after each shift or insertion.
        """
        for i in range(1, len(self.data)):  # Start from the second element
            key = self.data[i]  # Current element to be compared
            j = i - 1  # Start comparing with the element just before the current element

            # Shift elements of the sorted portion that are greater than 'key' to the right
            while j >= 0 and key < self.data[j]:
                self.data[j + 1] = self.data[j]
                self.update(j, j+1)  # Update the visualization with the shifted indices
                j -= 1  # Move to the previous element

            # Insert the 'key' in its correct position
            self.data[j + 1] = key
            self.update()  # Update the visualization after insertion

algorithms/C_InsertionSort/sort.py
WasinUddy/PySort
from visualizer import SortVisualizer

class MergeSort(SortVisualizer):
    """
    MergeSort class that extends the SortVisualizer class to visualize the merge sort algorithm.

    Attributes:
        data (list): A list of integers to be sorted.
    """

    def __init__(self, data):
        """
        Constructor for the MergeSort class.

        Args:
            data (list): A list of integers to be sorted.
        """
        super().__init__(data)

    def sort(self):
        """
        Public method to initiate the merge sort algorithm.
        """
        self._merge_sort(0, len(self.data) - 1)

    def _merge_sort(self, l, r):
        """
        Recursive method that divides the list into two halves, sorts them, 
        and then merges the sorted halves.

        Args:
            l (int): Starting index of the list segment.
            r (int): Ending index of the list segment.
        """
        if l < r:
            m = (l + r) // 2  # Calculate mid point of the list segment
            self._merge_sort(l, m)  # Sort the first half
            self._merge_sort(m + 1, r)  # Sort the second half
            self._merge(l, m, r)  # Merge the sorted halves

    def _merge(self, l, m, r):
        """
        Helper method to merge two sorted sublists into a single sorted sublist.

        Args:
            l (int): Starting index of the left sublist.
            m (int): Ending index of the left sublist (or mid-point).
            r (int): Ending index of the right sublist.
        """
        # Create temporary lists to hold the two sublists to be merged
        L = self.data[l:m+1]
        R = self.data[m+1:r+1]

        i = j = 0  # Initialize pointers for L and R
        k = l  # Initialize pointer for the main list

        # Merge the data back into the main list in sorted order
        while i < len(L) and j < len(R):
            if L[i] < R[j]:
                self.data[k] = L[i]
                i += 1
            else:
                self.data[k] = R[j]
                j += 1
            self.update()  # Update the visualization
            k += 1

        # If there are remaining elements in L, add them to the main list
        while i < len(L):
            self.data[k] = L[i]
            i += 1
            k += 1
            self.update()  # Update the visualization

        # If there are remaining elements in R, add them to the main list
        while j < len(R):
            self.data[k] = R[j]
            j += 1
            k += 1
            self.update()  # Update the visualization

algorithms/D_MergeSort/sort.py
WasinUddy/PySort
from visualizer import SortVisualizer

class QuickSort(SortVisualizer):
    """
    QuickSort class that extends the SortVisualizer class to visualize the quick sort algorithm.

    Attributes:
        data (list): A list of integers to be sorted.
    """

    def __init__(self, data):
        """
        Constructor for the QuickSort class.

        Args:
            data (list): A list of integers to be sorted.
        """
        super().__init__(data)

    def sort(self):
        """
        Public method to initiate the quick sort algorithm.
        """
        self._quicksort(0, len(self.data) - 1)

    def _quicksort(self, low, high):
        """
        Private recursive method to perform the quick sort.

        Args:
            low (int): Starting index of the segment to be sorted.
            high (int): Ending index of the segment to be sorted.
        """
        if low < high:
            pivot_index = self._partition(low, high)
            self._quicksort(low, pivot_index)
            self._quicksort(pivot_index + 1, high)

    def _partition(self, low, high):
        """
        Private method to partition the segment of the list using the pivot. 
        Elements smaller than the pivot will be on the left, and elements greater than the pivot will be on the right.

        Args:
            low (int): Starting index of the segment to be partitioned.
            high (int): Ending index of the segment to be partitioned.

        Returns:
            int: The index position of the pivot after partitioning.
        """
        pivot = self.data[low]
        left = low + 1
        right = high
        done = False

        while not done:
            # Increment 'left' index while the element at 'left' is less than the pivot
            while left <= right and self.data[left] <= pivot:
                left = left + 1
                self.update()

            # Decrement 'right' index while the element at 'right' is greater than the pivot
            while self.data[right] >= pivot and right >= left:
                right = right - 1
                self.update()

            # If there is an element larger on the left of the pivot and an element smaller on the right, swap them
            if right < left:
                done = True
            else:
                self.data[left], self.data[right] = self.data[right], self.data[left]
                self.update(left, right)

        # Swap the pivot with the element at the 'right' index
        self.data[low], self.data[right] = self.data[right], self.data[low]
        self.update(low, right)

        return right

algorithms/E_QuickSort/sort.py
WasinUddy/PySort
from visualizer import SortVisualizer

class HeapSort(SortVisualizer):
    """
    HeapSort class that extends the SortVisualizer class to visualize the heap sort algorithm.

    Attributes:
        data (list): A list of integers to be sorted.
    """

    def __init__(self, data):
        """
        Constructor for the HeapSort class.

        Args:
            data (list): A list of integers to be sorted.
        """
        super().__init__(data)

    def sort(self):
        """
        Implements the heap sort algorithm to sort the data in ascending order.
        The method first builds a max heap and then extracts the maximum element (root of the heap)
        repeatedly to get the sorted list.

        The visualization is updated after each swap.
        """
        n = len(self.data)
        
        # Build a max heap
        for i in range(n // 2 - 1, -1, -1):
            self._heapify(n, i)
        
        # Extract elements from the heap one by one
        for i in range(n-1, 0, -1):
            # Move the current root (maximum element) to the end of the list
            self.data[i], self.data[0] = self.data[0], self.data[i]
            self.update(i, 0)
            
            # Call _heapify on the reduced heap
            self._heapify(i, 0)

    def _heapify(self, n, i):
        """
        Utility function to turn the subtree rooted at index i into a max heap, 
        assuming the subtrees rooted at its children are already max heaps.

        Args:
            n (int): Total number of elements in the heap.
            i (int): Index of the current node.
        """
        largest = i  # Initialize largest as root
        l = 2 * i + 1  # Left child index
        r = 2 * i + 2  # Right child index
        
        # Check if left child exists and is greater than root
        if l < n and self.data[i] < self.data[l]:
            largest = l
        
        # Check if right child exists and is greater than the largest so far
        if r < n and self.data[largest] < self.data[r]:
            largest = r
        
        # If the largest is not root
        if largest != i:
            # Swap the root with the largest child
            self.data[i], self.data[largest] = self.data[largest], self.data[i]
            self.update(i, largest)
            
            # Recursively heapify the affected subtree
            self._heapify(n, largest)

algorithms/F_HeapSort/sort.py
WasinUddy/PySort
import matplotlib.pyplot as plt

class SortVisualizer:
    """
    A class for visualizing sorting algorithms on a set of data using Matplotlib.
    
    Attributes:
        data (list): The input data to be visualized and sorted.
        fig (matplotlib.figure.Figure): The Matplotlib figure for the visualization.
        ax (matplotlib.axes.Axes): The Matplotlib axis for the visualization.
        bar_rects (list): A list of bar rectangles representing the data elements in the visualization.
    """

    def __init__(self, data):
        """
        Initialize the SortVisualizer object.

        Args:
            data (list): A list of numerical data to be visualized and sorted.
        """
        self.data = data
        self.fig, self.ax = plt.subplots()
        self.bar_rects = self.ax.bar(range(len(data)), data)
        self.ax.set_xlim(0, len(data))
        self.ax.set_ylim(0, int(1.1 * max(data)))

    def update(self, swap1=None, swap2=None):
        """
        Update the visualization during sorting.

        Args:
            swap1 (int, optional): Index of the first element to be swapped (default: None).
            swap2 (int, optional): Index of the second element to be swapped (default: None).
        """
        for rect, val in zip(self.bar_rects, self.data):
            rect.set_height(val)

        if swap1 is not None and swap2 is not None:
            self.bar_rects[swap1].set_color('r')
            self.bar_rects[swap2].set_color('g')
        else:
            for rect in self.bar_rects:
                rect.set_color('b')

        plt.pause(0.02)
        self.fig.canvas.draw()

    def sort(self):
        """
        Sort method for sorting the data elements.
        This method should be implemented in derived classes.
        """
        raise NotImplementedError("Sort method not implemented!")

    def do_visual_sort(self):
        """
        Execute the sorting process and display the visualization.
        """
        plt.ion()
        self.sort()
        plt.ioff()
        plt.show()

visualizer.py
WasinUddy/PySort