diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml new file mode 100644 index 0000000..a10fa16 --- /dev/null +++ b/.github/workflows/pythonapp.yml @@ -0,0 +1,29 @@ +name: Python application + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Lint with flake8 + run: | + pip install flake8 + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with doctest + run: | + python -m doctest -v app_scripts/*.py sorters/*.py \ No newline at end of file diff --git a/.gitignore b/.gitignore index b6e4761..583eeef 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,12 @@ dmypy.json # Pyre type checker .pyre/ + +# Pycharm files +.idea +.venv + +*.csv +*.jpeg +*.png +*.jpg \ No newline at end of file diff --git a/README.md b/README.md index 77ece2e..ea2857d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,38 @@ -# sorting_performance -Try sorting algorithms and evaluate performance by time and number of steps + +## Why does this exist? + +- trying different sorting techniques to see difference in performance +- understand (if possible) trade-off between heavy computations and number of computations +(a-la would you fight 1 horse sized duck or 100 duck size horses) + +## How to run it? + +Please run in python 3 for best results. No special packages required. +Some testing/coverage based tests will be written. Those might need a +look see in the `requirements.txt` file + +### Sorting performance +Try sorting algorithms and evaluate performance by +- total time to solve +- number of steps taken to solve + +### Inspiration + +- local supermarket is giving out free football player cards +- each card has a number so that unique orders can be established +- this helps in keeping track of what cards we have and which ones we want to trade +- card numbers range from 1 to 250 +- arranging all cards in to packs is a daily chore + - **Pack 1** : unique cards (sorted) + - **Pack 2**: extra copies of some cards in pack 1 + +Doing this on the dining table, +I realized that both me and my son are using various methods +of sorting! Constantly trying new sorting methods to either +- *speed up the process* +- OR *slow it down and dumb it down so that we can do it while chatting or watching cartoons* + +## Credits + +- [reddit thread](https://www.reddit.com/r/learnpython/comments/exese6/what_are_some_of_the_projects_i_can_start_working/fg7skxp/) +- [P vs NP problem explanation video in youtube uses a sorting based example](https://youtu.be/EHp4FPyajKQ?t=515) \ No newline at end of file diff --git a/app_scripts/create_check_random_number_list.py b/app_scripts/create_check_random_number_list.py new file mode 100644 index 0000000..a9588ff --- /dev/null +++ b/app_scripts/create_check_random_number_list.py @@ -0,0 +1,71 @@ +import random + + +def generate_list( + min_number: int = 1, max_number: int = 1000000, count: int = 1000, uniqued_list: bool = True, +) -> list: + """ + This function will create a list of random numbers. + It accepts min number in list, max number in list and count of numbers in list. + Note; if `count` is None then it defaults to 1000 + + :param min_number: mininum value of single number in list of random numbers + :param max_number: maximum value of single number in list of random numbers + :param count: number of random numbers expected + :param uniqued_list: boolean of whether or not the returned random list is allowed to have duplicate values or not + :return: list of size `count` of random numbers in random order + """ + + if count is None: + count = 1000 + + random_numbers_list = [] + + while not len(random_numbers_list) >= count: + + temp = random.randint(min_number, max_number) + + if uniqued_list: + if temp in random_numbers_list: + continue + + random_numbers_list.append(temp) + + if not check_order(random_numbers_list)["random_bool"]: + # If the generated list is somehow ordered then run the generator + # until randomness found. Useful in test scenarios. + generate_list(min_number, max_number, count, uniqued_list) + + return random_numbers_list + + +def check_order(list_of_numbers: list) -> dict: + """ + Take a list of numbers and returns whether the list + was ordered in ascending manner or not + + :param list_of_numbers: + :return: dictionary with 1 key `random_bool`. + - Value True means the list is random. + - Value False means the list is ordered in an ascending manner. + + Doctest + + >>> check_order([1,2,3]) + {'random_bool': False} + + >>> check_order([2,2,3]) + {'random_bool': False} + + >>> check_order([3,2,3]) + {'random_bool': True} + + """ + + state_of_randomness = {"random_bool": False} + + for index, num in enumerate(list_of_numbers[:-1]): + if num > list_of_numbers[index + 1]: + state_of_randomness["random_bool"] = True + + return state_of_randomness diff --git a/app_scripts/print_scripts.py b/app_scripts/print_scripts.py new file mode 100644 index 0000000..2b9a1ca --- /dev/null +++ b/app_scripts/print_scripts.py @@ -0,0 +1,51 @@ +import csv +import logging +import uuid +from pathlib import Path + + +def print_sort_progress(lowest_number: int, step_count: int, debug: bool): + if debug: + logging.info(f"Lowest number in this round = {lowest_number}. Step_count = {step_count}") + + +def print_sort_results( + method_name: str, + time_taken_to_sort: float, + step_count: int, + sort_state: bool, + matches_known_solution: bool = None, + help_text: str = "", + create_csv: bool = False, +): + result_f_string = f"{method_name} {help_text} took {time_taken_to_sort} seconds to order in {step_count} steps. Check: Sort status = {sort_state}." + + if matches_known_solution is not None: + logging.info( + f"{result_f_string} Accurate (against known solution: {matches_known_solution})" + ) + else: + logging.info(result_f_string) + + if create_csv: + + temp = { + "run_id": uuid.uuid4(), + "method_name": method_name, + "time_taken": time_taken_to_sort, + "step_count": step_count, + "sort_state": sort_state, + } + + if Path().joinpath("perf.csv").exists(): + + with open("perf.csv", "a") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow(list(temp.values())) + + else: + + with open("perf.csv", "w") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow(list(temp.keys())) + csv_writer.writerow(list(temp.values())) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5e02c33 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +matplotlib +pandas \ No newline at end of file diff --git a/sort_algorithm_runner.py b/sort_algorithm_runner.py new file mode 100644 index 0000000..d285a63 --- /dev/null +++ b/sort_algorithm_runner.py @@ -0,0 +1,167 @@ +import logging + +from app_scripts.create_check_random_number_list import generate_list +from sorters.bubble_sort import bubble_sort as bu_s +from sorters.merge_sort import merge_sort as ms +from sorters.native_sort import native_sort as ns +from sorters.quick_sort import quick_sort as qs +from sorters.selection_sort_1 import selection_sort as ss1 +from sorters.selection_sort_2 import selection_sort as ss2 + +debug = False +count = None +# count = 500 + +logging.basicConfig( + # filename=f"{my_dir}/sort_runner.log", + level=logging.INFO, + format="%(asctime)s %(levelname)s %(message)s", +) + +if debug: + unique_random_list = generate_list(max_number=20, count=6, uniqued_list=True) + duplicate_allowed_random_list = generate_list(max_number=20, count=6, uniqued_list=False) +else: + unique_random_list = generate_list(count=count, uniqued_list=True) + duplicate_allowed_random_list = generate_list(count=count, uniqued_list=False) + +known_solution_unique_random_list = ss1( + unique_random_list, debug=debug, help_text="for unique numbers" +) +known_solution_duplicate_allowed_random_list = ss2( + duplicate_allowed_random_list, debug=debug, help_text="for non-unique numbers" +) + +bu_s( + unique_random_list, + known_solution_unique_random_list, + debug=debug, + help_text="for unique numbers", +) + +bu_s( + duplicate_allowed_random_list, + known_solution_duplicate_allowed_random_list, + debug=debug, + help_text="for non-unique numbers", +) + +ms( + unique_random_list, + known_solution_unique_random_list, + debug=debug, + help_text="for unique numbers", +) + +ms( + duplicate_allowed_random_list, + known_solution_duplicate_allowed_random_list, + debug=debug, + help_text="for non-unique numbers", +) + +ns( + unique_random_list, + known_solution_unique_random_list, + debug=debug, + help_text="for unique numbers", +) + +ns( + duplicate_allowed_random_list, + known_solution_duplicate_allowed_random_list, + debug=debug, + help_text="for non-unique numbers", +) + +qs( + unique_random_list, + known_solution_unique_random_list, + debug=debug, + help_text="for unique numbers", +) + +qs( + duplicate_allowed_random_list, + known_solution_duplicate_allowed_random_list, + debug=debug, + help_text="for non-unique numbers", +) + + +def create_graph(): + + for i in range(100): + unique_random_list = generate_list(count=count, uniqued_list=True) + known_solution_unique_random_list = ss1( + unique_random_list, debug=debug, help_text="for unique numbers", create_csv=True + ) + bu_s( + unique_random_list, + known_solution_unique_random_list, + debug=debug, + help_text="for unique numbers", + create_csv=True, + ) + ms( + unique_random_list, + known_solution_unique_random_list, + debug=debug, + help_text="for unique numbers", + create_csv=True, + ) + ns( + unique_random_list, + known_solution_unique_random_list, + debug=debug, + help_text="for unique numbers", + create_csv=True, + ) + qs( + unique_random_list, + known_solution_unique_random_list, + debug=debug, + help_text="for unique numbers", + create_csv=True, + ) + + +create_graph() + +## Experimental +# import pandas as pd +# import matplotlib.pyplot as plt +# all_data_df = pd.read_csv("perf.csv") +# perf_df = all_data_df.loc[:, ["method_name", "time_taken"]] +# steps_df = all_data_df.loc[:, ["method_name", "step_count"]] +# +# fig, (ax1, ax2) = plt.subplots(1, 2) +# +# perf_mer = perf_df[perf_df["method_name"] == "Merge sort"].loc[:,"time_taken"].to_numpy() +# perf_quk = perf_df[perf_df["method_name"] == "Quick sort"].loc[:,"time_taken"].to_numpy() +# perf_bub = perf_df[perf_df["method_name"] == "Bubble sort"].loc[:,"time_taken"].to_numpy() +# perf_nat = perf_df[perf_df["method_name"] == "Native sort"].loc[:,"time_taken"].to_numpy() +# perf_sel = perf_df[perf_df["method_name"] == "Selection sort 1.0"].loc[:,"time_taken"].to_numpy() +# ax1.set_title("Box plot of performance (lower is better)") +# ax1.set_xlabel("sorters") +# ax1.set_ylabel("time in seconds") +# ax1.set_xticklabels(["Merge", "Quick", "Bubble", "Native", "Selection 1.0"]) +# ax1.boxplot((perf_mer, perf_bub, perf_nat, perf_sel, perf_quk)) +# +# +# steps_mer = steps_df[steps_df["method_name"] == "Merge sort"].loc[:,"step_count"].to_numpy() +# steps_quk = steps_df[steps_df["method_name"] == "Quick sort"].loc[:,"step_count"].to_numpy() +# steps_bub = steps_df[steps_df["method_name"] == "Bubble sort"].loc[:,"step_count"].to_numpy() +# steps_nat = steps_df[steps_df["method_name"] == "Native sort"].loc[:,"step_count"].to_numpy() +# steps_sel = steps_df[steps_df["method_name"] == "Selection sort 1.0"].loc[:,"step_count"].to_numpy() +# ax2.set_title("Box plot of steps (lower is better)") +# ax2.set_xlabel("sorters") +# ax2.set_ylabel("count") +# ax2.set_xticklabels(["Merge", "Quick", "Bubble", "Native", "Selection 1.0"]) +# ax2.boxplot((steps_mer, steps_bub, steps_nat, steps_sel, steps_quk)) + +##fig.savefig("x.jpeg", orientation="landscape", bbox_inches="tight") +## fig.show() + +# TODO: Heap Sort https://en.wikipedia.org/wiki/Heapsort +# TODO: Bucket Sort (this is the method that I used to sort cards when I need to sort under distraction. Takes longer because more steps required.) https://en.wikipedia.org/wiki/Bucket_sort diff --git a/sorters/bubble_sort.py b/sorters/bubble_sort.py new file mode 100644 index 0000000..8a3ec76 --- /dev/null +++ b/sorters/bubble_sort.py @@ -0,0 +1,82 @@ +import copy +from datetime import datetime + +from app_scripts.create_check_random_number_list import check_order +from app_scripts.print_scripts import print_sort_results + + +# Method 2: Bubble sort (as done in youtube linked video in Readme) https://en.wikipedia.org/wiki/Bubble_sort +# Settings: duplicate/unique agnostic + + +def bubble_sort( + random_list: list, + known_solution_unique_random_list: list, + debug: bool, + help_text: str = "", + create_csv: bool = False, +): + """ + + :param random_list: + :param known_solution_unique_random_list: + :param debug: + :param help_text: + :return: + + Doctest + + >>> bubble_sort([5,4,3,2,1],[1,2,3,4,5], debug=False) + [1, 2, 3, 4, 5] + + >>> bubble_sort([3,3,2,1,2,3], [1,2,2,3,3,3], debug=False) + [1, 2, 2, 3, 3, 3] + + """ + method_name = "Bubble sort" + step_count = 0 + start_time = datetime.now() + + cache_random_list = copy.deepcopy(random_list) + ordered_list = [] + count_cache_list = len(cache_random_list) + + while count_cache_list > 1: + for num_index in range(count_cache_list - 1): + current_num = cache_random_list[num_index] + next_num = cache_random_list[num_index + 1] + + if current_num > next_num: + cache_random_list[num_index] = next_num + cache_random_list[num_index + 1] = current_num + step_count += 1 + else: + cache_random_list[num_index] = current_num + cache_random_list[num_index + 1] = next_num + step_count += 1 + + ordered_list.insert(0, cache_random_list[-1]) + cache_random_list = copy.deepcopy(cache_random_list[:-1]) + count_cache_list = len(cache_random_list) + + if debug: + print("\t\t\tgrowing ordered_list", ordered_list) + + ordered_list = cache_random_list + ordered_list + if debug: + print("\t\t\t\tordered_list", ordered_list) + + time_taken_to_sort = round((datetime.now() - start_time).total_seconds(), 4) + sort_state = check_order(ordered_list)["random_bool"] is False + matches_known_solution = ordered_list == known_solution_unique_random_list + print_sort_results( + method_name=method_name, + time_taken_to_sort=time_taken_to_sort, + step_count=step_count, + sort_state=sort_state, + matches_known_solution=matches_known_solution, + help_text=help_text, + create_csv=create_csv, + ) + + return ordered_list diff --git a/sorters/merge_sort.py b/sorters/merge_sort.py new file mode 100644 index 0000000..b2f633f --- /dev/null +++ b/sorters/merge_sort.py @@ -0,0 +1,122 @@ +import copy +from datetime import datetime + +from app_scripts.create_check_random_number_list import check_order +from app_scripts.print_scripts import print_sort_results + + +# Method 3: Merge sort (as done in youtube linked video in Readme) https://en.wikipedia.org/wiki/Merge_sort +# Settings: duplicate/unique agnostic + + +def get_combined_chunk(chunk_a: list, chunk_b: list) -> dict: + """ + + :param chunk_a: + :param chunk_b: + :return: + + Doctest + + >>> get_combined_chunk(chunk_a = [2], chunk_b = [1]) + {'chunk_ordered_list': [1, 2], 'step_count': 1} + + >>> get_combined_chunk(chunk_a = [2,4,5,8], chunk_b = [3,4,7]) + {'chunk_ordered_list': [2, 3, 4, 4, 5, 7, 8], 'step_count': 6} + + """ + chunk_ordered_list = [] + step_count = 0 + + while chunk_a and chunk_b: + + if chunk_a[0] > chunk_b[0]: + chunk_ordered_list.append(chunk_b[0]) + chunk_b = chunk_b[1:] + step_count += 1 + else: + chunk_ordered_list.append(chunk_a[0]) + chunk_a = chunk_a[1:] + step_count += 1 + + chunk_ordered_list.extend(chunk_a) + chunk_ordered_list.extend(chunk_b) + + return {"chunk_ordered_list": chunk_ordered_list, "step_count": step_count} + + +def merge_sort( + random_list: list, + known_solution_unique_random_list: list, + debug: bool, + help_text: str = "", + create_csv: bool = False, +): + """ + + :param random_list: + :param known_solution_unique_random_list: + :param debug: + :param help_text: + :return: + + Doctest + + >>> merge_sort([5,4,3,2,1],[1,2,3,4,5], debug=False) + [1, 2, 3, 4, 5] + + >>> merge_sort([3,3,2,1,2,3], [1,2,2,3,3,3], debug=False) + [1, 2, 2, 3, 3, 3] + + """ + method_name = "Merge sort" + step_count = 0 + start_time = datetime.now() + ordered_list = copy.deepcopy(random_list) + chunk_size = 1 + + random_count = len(random_list) + + while chunk_size <= random_count: + cache_random_list = copy.deepcopy(ordered_list) + ordered_list = [] + + chunk_indexes = [i for i in range(0, random_count, chunk_size)] + [random_count] + step_count += len(chunk_indexes) + chunk_pos = list(zip(chunk_indexes[:-1], chunk_indexes[1:])) + chunks = [cache_random_list[i[0] : i[1]] for i in chunk_pos] + step_count += len(chunks) + + for chunk_index, a_chunk in enumerate(chunks[:-1]): + if chunk_index % 2 == 0: + temp = get_combined_chunk(a_chunk, chunks[chunk_index + 1]) + ordered_list.extend(temp["chunk_ordered_list"]) + step_count += temp["step_count"] + + leftover_random_numbers = cache_random_list[len(ordered_list) : len(cache_random_list)] + ordered_list.extend(leftover_random_numbers) + + chunk_size = chunk_size * 2 + + if debug: + print("current_chunk_size =", chunk_size) + print("\tcache_random_list", cache_random_list) + print("\tchunk_indexes", chunk_indexes) + print("\tchunk_pos", chunk_pos) + print("\tchunks", chunks) + print("\t\t`ordered_list`, next `cache_random_list`", ordered_list) + + time_taken_to_sort = round((datetime.now() - start_time).total_seconds(), 4) + sort_state = check_order(ordered_list)["random_bool"] is False + matches_known_solution = ordered_list == known_solution_unique_random_list + print_sort_results( + method_name=method_name, + time_taken_to_sort=time_taken_to_sort, + step_count=step_count, + sort_state=sort_state, + matches_known_solution=matches_known_solution, + help_text=help_text, + create_csv=create_csv, + ) + + return ordered_list diff --git a/sorters/native_sort.py b/sorters/native_sort.py new file mode 100644 index 0000000..5cab60b --- /dev/null +++ b/sorters/native_sort.py @@ -0,0 +1,55 @@ +from datetime import datetime + +from app_scripts.create_check_random_number_list import check_order +from app_scripts.print_scripts import print_sort_results + + +# Method 4: Native sort (as done by pythons sorted method) +# Settings: duplicate/unique agnostic + + +def native_sort( + random_list: list, + known_solution_unique_random_list: list, + debug: bool, + help_text: str = "", + create_csv: bool = False, +): + """ + + :param random_list: + :param known_solution_unique_random_list: + :param debug: + :param help_text: + :return: + + Doctest + + >>> native_sort([5,4,3,2,1],[1,2,3,4,5], debug=False) + [1, 2, 3, 4, 5] + + >>> native_sort([3,3,2,1,2,3], [1,2,2,3,3,3], debug=False) + [1, 2, 2, 3, 3, 3] + + """ + method_name = "Native sort" + step_count = 1 + start_time = datetime.now() + ordered_list = sorted(random_list) + if debug: + print("\t\t\t\tordered_list", ordered_list) + + time_taken_to_sort = round((datetime.now() - start_time).total_seconds(), 4) + sort_state = check_order(ordered_list)["random_bool"] is False + matches_known_solution = ordered_list == known_solution_unique_random_list + print_sort_results( + method_name=method_name, + time_taken_to_sort=time_taken_to_sort, + step_count=step_count, + sort_state=sort_state, + matches_known_solution=matches_known_solution, + help_text=help_text, + create_csv=create_csv, + ) + + return ordered_list diff --git a/sorters/quick_sort.py b/sorters/quick_sort.py new file mode 100644 index 0000000..6b6adb3 --- /dev/null +++ b/sorters/quick_sort.py @@ -0,0 +1,238 @@ +import copy +from datetime import datetime + +from app_scripts.create_check_random_number_list import check_order +from app_scripts.print_scripts import print_sort_results + + +# Method 5: Quick sort https://en.wikipedia.org/wiki/Quicksort +# Settings: duplicate/unique agnostic + + +def find_pivot(random_list: list) -> dict: + """ + + :param random_list: + :return: + + Doctest + + >>> find_pivot([7,3,5,12,18,9,2]) + {'step_count': 2, 'pivot': 7, 'pivot_index': 0} + + >>> find_pivot([7,3,5,18,9,2]) + {'step_count': 2, 'pivot': 7, 'pivot_index': 0} + + >>> find_pivot([2,3,18,7,9,5]) + {'step_count': 3, 'pivot': 7, 'pivot_index': 3} + + >>> find_pivot([14,8,15,15,18,14]) + {'step_count': 9, 'pivot': 15, 'pivot_index': 2} + """ + + indexes = { + "begin": 0, + "middle": int(len(random_list) / 2), + "pre_middle": int(len(random_list) / 2) - 1, + "end": len(random_list) - 1, + } + + vals_to_chose_from = { + "begin": random_list[indexes["begin"]], + "middle": random_list[indexes["middle"]], + "end": random_list[indexes["end"]], + } + if not len(random_list) % 2: + vals_to_chose_from["pre_middle"] = random_list[indexes["pre_middle"]] + + temp = {"step_count": 1, "pivot": None, "pivot_index": None} + + step_count = 1 + for pivot_index_key, pivot in vals_to_chose_from.items(): + step_count += 1 + if pivot != min(list(vals_to_chose_from.values())) and pivot != max( + list(vals_to_chose_from.values()) + ): + temp = { + "step_count": step_count, + "pivot": pivot, + "pivot_index": indexes[pivot_index_key], + } + return temp + + if None in list(temp.values()): + max_val = max(list(vals_to_chose_from.values())) + for i in list(vals_to_chose_from.keys()): + step_count += 1 + if vals_to_chose_from[i] == max_val: + max_val_index = indexes[i] + return {"step_count": step_count, "pivot": max_val, "pivot_index": max_val_index} + + +def sort_wrt_pivot(random_list: list, pivot: int, debug: bool = False) -> dict: + """ + + :param random_list: + :param pivot: + :return: + + Doctest + + >>> sort_wrt_pivot([7,3,5,12,18,9,2], 8) + {'step_count': 23, 'less_random_list': [[7, 3, 5, 2], [8], [18, 9, 12]]} + + >>> sort_wrt_pivot([12,18,9,2,7,3,5], 8) + {'step_count': 39, 'less_random_list': [[5, 3, 7, 2], [8], [9, 18, 12]]} + + >>> sort_wrt_pivot([4,4,2], 4) + {'step_count': 9, 'less_random_list': [[2], [4], [4, 4]]} + """ + step_count = 0 + cache_random_list = copy.deepcopy(random_list) + + while True: + + for val_ind, val in enumerate(cache_random_list): + step_count += 1 + if val >= pivot: + item_from_left = val + item_from_left_ind = val_ind + break + + for val_ind, val in enumerate(cache_random_list): + step_count += 1 + if val <= pivot: + item_from_right = val + item_from_right_ind = val_ind + + if debug: + print("item_from_left", item_from_left) + print("item_from_left_ind", item_from_left_ind) + print("item_from_right", item_from_right) + print("item_from_right_ind", item_from_right_ind) + + if item_from_right_ind <= item_from_left_ind or item_from_right == item_from_left: + return { + "step_count": step_count, + "less_random_list": [ + cache_random_list[:item_from_left_ind], + [pivot], + cache_random_list[item_from_left_ind:], + ], + } + + else: + cache_random_list[item_from_right_ind] = item_from_left + cache_random_list[item_from_left_ind] = item_from_right + + +def recursive_sorter(list_of_random_lists: list, step_count: int = 0, debug: bool = False) -> dict: + """ + + :param list_of_random_lists: + :param step_count: + :return: + + Doctest + + >>> recursive_sorter([[7,3,5,12,18,9,2]]) + {'ordered_list_of_lists': [[2], [3], [5], [7], [9], [12], [18]], 'step_count': 41} + """ + + new_list_of_random_lists = [] + sorting_action_carried_out = False + + if debug and not step_count: + print("Beginning of Quick sort.") + print("Random list: ", list_of_random_lists) + elif debug and step_count: + print("\tRandom list: ", list_of_random_lists) + + for random_list in list_of_random_lists: + + if len(random_list) < 3: + try: + if random_list[0] > random_list[1]: + random_list.reverse() + except IndexError: + pass + new_list_of_random_lists.append(random_list) + else: + sorting_action_carried_out = True + pivot_data = find_pivot(random_list) + step_count += pivot_data["step_count"] + pivot = pivot_data["pivot"] + pivot_index = pivot_data["pivot_index"] + + if debug: + print("\t\t pivot", pivot) + + new_random_list = copy.deepcopy(random_list) + del new_random_list[pivot_index] + + partition_data = sort_wrt_pivot(new_random_list, pivot, debug) + step_count += partition_data["step_count"] + new_list_of_random_lists.extend(partition_data["less_random_list"]) + + current_result = {"ordered_list_of_lists": new_list_of_random_lists, "step_count": step_count} + + if sorting_action_carried_out: + if debug: + print("\tnew_list_of_random_lists", new_list_of_random_lists) + return recursive_sorter(new_list_of_random_lists, step_count, debug) + else: + return current_result + + +def quick_sort( + random_list: list, + known_solution_unique_random_list: list, + debug: bool, + help_text: str = "", + create_csv: bool = False, +): + """ + + :param random_list: + :param known_solution_unique_random_list: + :param debug: + :param help_text: + :return: + + Doctest + + >>> quick_sort([5,4,3,2,1],[1,2,3,4,5], debug=False) + [1, 2, 3, 4, 5] + + >>> quick_sort([3,3,2,1,2,3], [1,2,2,3,3,3], debug=False) + [1, 2, 2, 3, 3, 3] + + """ + method_name = "Quick sort" + step_count = 0 + start_time = datetime.now() + ordered_list = [] + + ordered_list_dict = recursive_sorter([random_list], debug=debug) + step_count = ordered_list_dict["step_count"] + ordered_list_of_lists = ordered_list_dict["ordered_list_of_lists"] + for i in ordered_list_of_lists: + ordered_list.extend(i) + + if debug: + print("\t\t\t\tordered_list", ordered_list) + + time_taken_to_sort = round((datetime.now() - start_time).total_seconds(), 4) + sort_state = check_order(ordered_list)["random_bool"] is False + matches_known_solution = ordered_list == known_solution_unique_random_list + print_sort_results( + method_name=method_name, + time_taken_to_sort=time_taken_to_sort, + step_count=step_count, + sort_state=sort_state, + matches_known_solution=matches_known_solution, + help_text=help_text, + create_csv=create_csv, + ) + + return ordered_list diff --git a/sorters/selection_sort_1.py b/sorters/selection_sort_1.py new file mode 100644 index 0000000..b770104 --- /dev/null +++ b/sorters/selection_sort_1.py @@ -0,0 +1,64 @@ +import copy +from datetime import datetime + +from app_scripts.create_check_random_number_list import check_order +from app_scripts.print_scripts import print_sort_progress, print_sort_results + + +# Method 1: Selection sort. The method that is easiest. I used it to sort the cards. this was the quickest for me. +# Settings: only unique numbers + + +def selection_sort( + random_list: list, debug: bool, help_text: str = "", create_csv: bool = False +) -> list: + """ + + :param random_list: + :param debug: + :param help_text: + :return: + + Doctest + + >>> selection_sort([5,4,3,2,1], debug=False) + [1, 2, 3, 4, 5] + + >>> selection_sort([3,3,2,1,2,3], debug=False) + [1, 2, 2, 3, 3, 3] + + """ + method_name = "Selection sort 1.0" + step_count = 0 + start_time = datetime.now() + + ordered_list = [] + + cache_random_list = copy.deepcopy(random_list) + + while len(ordered_list) != len(random_list): + lowest_number = cache_random_list[0] + for i in cache_random_list: + if i < lowest_number: + step_count += 1 + lowest_number = i + else: + step_count += 1 + print_sort_progress(lowest_number, step_count, debug=debug) + + ordered_list.append(lowest_number) + cache_random_list.remove(lowest_number) + + time_taken_to_sort = round((datetime.now() - start_time).total_seconds(), 4) + sort_state = check_order(ordered_list)["random_bool"] is False + known_solution_unique_random_list = copy.deepcopy(ordered_list) + print_sort_results( + method_name=method_name, + time_taken_to_sort=time_taken_to_sort, + step_count=step_count, + sort_state=sort_state, + help_text=help_text, + create_csv=create_csv, + ) + + return known_solution_unique_random_list diff --git a/sorters/selection_sort_2.py b/sorters/selection_sort_2.py new file mode 100644 index 0000000..891f76f --- /dev/null +++ b/sorters/selection_sort_2.py @@ -0,0 +1,75 @@ +import copy +from datetime import datetime + +from app_scripts.create_check_random_number_list import check_order +from app_scripts.print_scripts import print_sort_progress, print_sort_results + + +# Method 1.1: Selection sort. +# Settings: duplicate numbers allowed + + +def selection_sort( + random_list: list, debug: bool, help_text: str = "", create_csv: bool = False +) -> list: + """ + + :param random_list: + :param debug: + :param help_text: + :return: + + Doctest + + >>> selection_sort([5,4,3,2,1], debug=False) + [1, 2, 3, 4, 5] + + >>> selection_sort([3,3,2,1,2,3], debug=False) + [1, 2, 2, 3, 3, 3] + """ + method_name = "Selection sort 1.1" + step_count = 0 + start_time = datetime.now() + + ordered_list = [] + + cache_random_list = copy.deepcopy(random_list) + + while len(ordered_list) != len(random_list): + lowest_number = cache_random_list[0] + for i in cache_random_list: + if i < lowest_number: + step_count += 1 + lowest_number = i + else: + step_count += 1 + print_sort_progress(lowest_number, step_count, debug=debug) + + lowest_number_list = [] + for i in cache_random_list: + step_count += 1 + if i == lowest_number: + step_count += 1 + lowest_number_list.append(i) + else: + step_count += 1 + + ordered_list.extend(lowest_number_list) + + for i in lowest_number_list: + step_count += 1 + cache_random_list.remove(i) + + time_taken_to_sort = round((datetime.now() - start_time).total_seconds(), 4) + sort_state = check_order(ordered_list)["random_bool"] is False + known_solution_duplicate_allowed_random_list = copy.deepcopy(ordered_list) + print_sort_results( + method_name=method_name, + time_taken_to_sort=time_taken_to_sort, + step_count=step_count, + sort_state=sort_state, + help_text=help_text, + create_csv=create_csv, + ) + + return known_solution_duplicate_allowed_random_list