forked from MIT-Emerging-Talent/ET6-practice-code-review
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #51 from MIT-Emerging-Talent/restore-mistakenly-de…
…leted-merge-sort restore mistakenly deleted merge_sort.py and test_merge_sort.py
- Loading branch information
Showing
2 changed files
with
253 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
""" | ||
A module for sorting non empty collection of build-in elementary Python data types | ||
(int, float, str, bool) using merge sorting algorithm. | ||
Module contents: | ||
- merge_sort: function that splits the collection and launches the merge, | ||
- merge: function that merges two sorted collections into one sorted collection. | ||
Created on 2024-01-05 | ||
Author: Oleksandr Maksymikhin | ||
""" | ||
|
||
from typing import TypeVar | ||
|
||
# from solutions.merge import merge | ||
|
||
T = TypeVar("T", int, float, str, bool) | ||
|
||
|
||
def merge_sort(input_collection: list[T]) -> list[T]: | ||
"""Sort collection using merge sorting algorithm. | ||
Sort elements in collection by splitting the collection in two parts and launch merge. | ||
Parameters: | ||
- input_collection: list[T], collection of unsorted elements of data types (int, float, string, bool). | ||
Returns -> list[T], collection of sorted elements of data types (int, float, string, bool). | ||
Raises: | ||
- AssertionError: if input is not a collection. | ||
- AssertionError: if input collection is empty. | ||
- AssertionError: if input is not a data type processed by module (int, float, string, bool). | ||
- AssertionError: if collection contains elements of different data types (non-homogeneous). | ||
Examples: | ||
>>> merge_sort([1]) | ||
[1] | ||
>>> merge_sort([1, 3, 2]) | ||
[1, 2, 3] | ||
>>> merge_sort([3, 2, 100500, 1]) | ||
[1, 2, 3, 100500] | ||
>>> merge_sort(['banana', 'apple', 'cherry']) | ||
['apple', 'banana', 'cherry'] | ||
""" | ||
|
||
# defensive assertion to check that input is a collection list | ||
assert isinstance(input_collection, list), "Input is not a collection" | ||
|
||
# defensive assertion to check that input collection is not empty | ||
assert len(input_collection) > 0, "Collection is empty" | ||
|
||
# defensive assertion to check that input data types are any of (int, float, string, bool) | ||
assert ( | ||
isinstance(input_collection[0], int) | ||
or isinstance(input_collection[0], float) | ||
or isinstance(input_collection[0], str) | ||
or isinstance(input_collection[0], bool) | ||
), ( | ||
"Input data types is not processed. Processed data types (int, float, string, bool)" | ||
) | ||
|
||
# defensive assertion to check that collection is homogeneous | ||
assert all( | ||
isinstance(item, type(input_collection[0])) for item in input_collection | ||
), "Collection is not homogeneous" | ||
|
||
# copy collection to avoid side effect | ||
collection = input_collection.copy() | ||
if len(collection) < 2: | ||
return input_collection | ||
|
||
# divide collection to two parts | ||
split_index = len(collection) // 2 | ||
|
||
left = merge_sort(collection[:split_index]) | ||
right = merge_sort(collection[split_index:]) | ||
|
||
# return merged collection, sorting left and right parts | ||
return merge(left, right) | ||
|
||
|
||
def merge(left: list[T], right: list[T]) -> list[T]: | ||
"""Merge two sorted collections into one sorted collection. | ||
Combine two sorted homogeneous collections into one sorted collection | ||
Parameters: | ||
- left: list[T], first sorted collection of data types (int, float, string, bool). | ||
- right: list[T], second sorted collection of data types (int, float, string, bool). | ||
Returns -> list[T], sorted collection of data types (int, float, string, bool). | ||
Raises: | ||
- AssertionError: left is not a collection. | ||
- AssertionError: right is not a collection. | ||
- AssertionError: left collection contains elements of different data types (non-homogeneous). | ||
- AssertionError: right collection contains elements of different data types (non-homogeneous). | ||
- AssertionError: left and right contain elements of different data types(non-homogeneous). | ||
Examples: | ||
>>> merge([2], [1]) | ||
[1, 2] | ||
>>> merge([1, 5], [2, 6]) | ||
[1, 2, 5, 6] | ||
>>> merge([2, 5, 100500], [1, 9]) | ||
[1, 2, 5, 9, 100500] | ||
""" | ||
# defensive assertion to check that input is a collections are lists | ||
assert isinstance(left, list), "Input left is not a collection" | ||
assert isinstance(right, list), "Input right is not a collection" | ||
|
||
# defensive assertion to check that input collections are homogeneous | ||
assert all(isinstance(item, type(left[0])) for item in left), ( | ||
"Input left is not homogeneous" | ||
) | ||
assert all(isinstance(item, type(right[0])) for item in right), ( | ||
"Input right is not homogeneous" | ||
) | ||
assert isinstance(left[0], type(right[0])), ( | ||
"Input left and right are not homogeneous" | ||
) | ||
|
||
# check if left or right collection is empty | ||
if len(left) == 0: | ||
return right | ||
if len(right) == 0: | ||
return left | ||
|
||
# create empty collection for merge | ||
merged_collection = [] | ||
# create indexed to traverse collections | ||
index_left = index_right = 0 | ||
|
||
# merge collections until length of merged collection is smaller | ||
# then length of left and right collections | ||
while len(merged_collection) < len(left) + len(right): | ||
if left[index_left] <= right[index_right]: | ||
merged_collection.append(left[index_left]) | ||
index_left += 1 | ||
else: | ||
merged_collection.append(right[index_right]) | ||
index_right += 1 | ||
|
||
# if left or right collection becomes empty | ||
# add elements of the other collection and break loop | ||
if index_left == len(left): | ||
merged_collection += right[index_right:] | ||
break | ||
if index_right == len(right): | ||
merged_collection += left[index_left:] | ||
break | ||
|
||
return merged_collection |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
""" | ||
Test module for merge_sort() function. | ||
Test sorting of homogeneous collections types (int, float, string, bool) using merge algorithm. | ||
Test categories: | ||
- Standard cases: lists of type int with different lengths | ||
- Edge cases: single element type int | ||
- Different data type: str, bool | ||
- Defensive tests: | ||
- side effects protection | ||
- input is not a collection | ||
- input of empty collection | ||
- different data types in the input collection (non-homogeneous) | ||
- input data types that are not processed by module | ||
Created on 2024-01-05 | ||
Author: Oleksandr Maksymikhin | ||
""" | ||
|
||
import unittest | ||
|
||
from ..merge_sort import merge_sort | ||
|
||
|
||
class TestMergeSort(unittest.TestCase): | ||
"""Test the merge_sort function.""" | ||
|
||
def test_single_int_element_list(self): | ||
"""It should return [1] for input [1]""" | ||
actual = merge_sort([1]) | ||
expected = [1] | ||
self.assertEqual(actual, expected) | ||
|
||
def test_two_int_elements_list(self): | ||
"""It should return [1, 2] for input [2, 1]""" | ||
actual = merge_sort([2, 1]) | ||
expected = [1, 2] | ||
self.assertEqual(actual, expected) | ||
|
||
def test_three_int_elements_list(self): | ||
"""It should return [1, 2, 3] for input [3, 2, 1]""" | ||
actual = merge_sort([3, 2, 1]) | ||
expected = [1, 2, 3] | ||
self.assertEqual(actual, expected) | ||
|
||
def test_four_int_elements_list_big_number(self): | ||
"""It should return [1, 2, 3, 100500] for input [2, 3, 100500, 1]""" | ||
actual = merge_sort([2, 3, 100500, 1]) | ||
expected = [1, 2, 3, 100500] | ||
self.assertEqual(actual, expected) | ||
|
||
def test_char_elements_list(self): | ||
"""It should return ["a", "b", "c", "d"] for input ["b", "d", "a", "c"]""" | ||
actual = merge_sort(["b", "d", "a", "c"]) | ||
expected = ["a", "b", "c", "d"] | ||
self.assertEqual(actual, expected) | ||
|
||
def test_str_elements_list(self): | ||
"""It should return ["apple", "banana", "cabbage", "daikon"] for input ["daikon", "cabbage", "banana", "apple"]""" | ||
actual = merge_sort(["daikon", "cabbage", "banana", "apple"]) | ||
expected = ["apple", "banana", "cabbage", "daikon"] | ||
self.assertEqual(actual, expected) | ||
|
||
def test_bool_elements_list(self): | ||
"""It should return [False, False, True, True] for input [True, False, False, True]""" | ||
actual = merge_sort([True, False, False, True]) | ||
expected = [False, False, True, True] | ||
self.assertEqual(actual, expected) | ||
|
||
def test_side_effect_protection(self): | ||
"""It should return [3, 2, 1] of initial input""" | ||
input_list = [3, 2, 1, 100500] | ||
copy_for_sorting = input_list.copy() | ||
merge_sort(copy_for_sorting) | ||
self.assertEqual(input_list, [3, 2, 1, 100500]) | ||
|
||
def test_non_collection_input(self): | ||
"""It should raise an assertion error if the input is not a collection""" | ||
with self.assertRaises(AssertionError): | ||
merge_sort("banana") | ||
|
||
def test_non_homogeneous_collection_input(self): | ||
"""It should raise an assertion error if the collection is non-homogeneous""" | ||
with self.assertRaises(AssertionError): | ||
merge_sort([3, 2, "one"]) | ||
|
||
def test_input_of_empty_collection(self): | ||
"""It should raise an assertion error if the input data type is out of processed type""" | ||
with self.assertRaises(AssertionError): | ||
merge_sort([]) | ||
|
||
def test_input_of_non_processed_data_type(self): | ||
"""It should raise an assertion error if the input data type is out of processed type""" | ||
with self.assertRaises(AssertionError): | ||
merge_sort([{1, "one"}, {2, "two"}]) |