Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 77 additions & 39 deletions complexity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import time
import numpy as np

# Profiler to measure execution time and memory usage
def time_and_space_profiler(func):
def wrapper(*args, **kwargs):
start_time = time.time()
Expand All @@ -12,61 +13,98 @@ def wrapper(*args, **kwargs):
mem_after = memory_usage()[0]
end_time = time.time()

#print(f"Execution time: {end_time - start_time} seconds")
#print(f"Memory usage: {mem_after - mem_before} MiB")

return func.__name__,result,end_time-start_time,mem_after - mem_before
return func.__name__, result, end_time - start_time, mem_after - mem_before
return wrapper

# calculate the least squares to fit the complexity fucntion
# inspired by https://github.com/Alfex4936/python-bigO-calculator
def leastSquares(x,y,func):
sigma_gn_squared = func(x)**2
sigma_gn_y = func(x) * y
coef = sigma_gn_y.sum() / sigma_gn_squared.sum()
rms = np.sqrt(((y - coef * func(x))** 2).mean()) / y.mean()
# Calculate the least squares to fit the complexity function
def leastSquares(x, y, func):
sigma_gn_squared = (func(x) ** 2).sum()
sigma_gn_y = (func(x) * y).sum()
coef = sigma_gn_y / sigma_gn_squared
rms = np.sqrt(((y - coef * func(x)) ** 2).mean()) / y.mean()
func_data = coef * func(x)

return rms, func_data , coef

return rms, func_data, coef


# print the complexity of the algorithm using array lenght and time - or comparison -
# Determine the complexity of the algorithm using array length and time/comparison data
def get_complexity(x, y):
# Define common complexity functions
def _1(x): return np.ones(x.shape)
def n(x): return x
def n_2(x): return x ** 2
def n_3(x): return x ** 3
def log2(x): return np.log2(x)
def nlog2(x): return x * np.log2(x)

funcs = [_1, n, n_2, n_3, log2, nlog2]
complexity_names = ['O(1)', 'O(n)', 'O(n^2)', 'O(n^3)', 'O(log n)', 'O(n log n)']

# Fit each function and find the best fit
best_fit, _, _ = leastSquares(x, y, funcs[0])
best_name = complexity_names[0]

def _1(x):
return np.ones(x.shape)
for func, name in zip(funcs[1:], complexity_names[1:]):
new_fit, _, _ = leastSquares(x, y, func)
if new_fit < best_fit:
best_fit = new_fit
best_name = name

def n(x):
return x
return best_name

def n_2(x):
return x**2
# Generate random data for testing
def generate_test_data(lengths, tests_per_length=5):
np.random.seed(42)
tests = []

for length in lengths:
for _ in range(tests_per_length):
array = np.sort(np.random.randint(1, 4 * length, size=length))
target = np.random.randint(1, 4 * length)
tests.append((length, array, target))

return tests

# Logic to test algorithms and determine the best one based on complexity
def test_algorithms(algorithms, test_data):
results = []
for length, array, target in test_data:
for func in algorithms:
func_name, result, exec_time, mem_usage = func(array, target)
results.append((func_name, length, exec_time, mem_usage, result))

# Organize results into arrays for analysis
complexities = {}
for func_name in set(r[0] for r in results):
lengths = np.array([r[1] for r in results if r[0] == func_name])
times = np.array([r[2] for r in results if r[0] == func_name])
best_fit = get_complexity(lengths, times)
complexities[func_name] = best_fit

def n_3(x):
return x**3
# Find the algorithm with the best (lowest) complexity
best_algorithm = min(complexities, key=lambda k: complexities[k])

def log2(x):
return np.log2(x)
return complexities, best_algorithm

def nlog2(x):
return x * np.log2(x)

# Example usage
if __name__ == "__main__":
# Define algorithms to test
@time_and_space_profiler
def dummy_algorithm(val, target):
return np.searchsorted(val, target)

funcs = [_1,n,n_2,n_3,log2,nlog2]
complexity_names = ['O(1)','O(n)','O(n2)','O(n3)','O(log2)','O(nlog2)']
algorithms = [dummy_algorithm]

best_fit, _ , _= leastSquares(x,y,funcs[0])
best_name = complexity_names[0]
# Generate test data
lengths = np.array([1000, 5000, 10000, 50000, 100000])
test_data = generate_test_data(lengths)

# Test algorithms and find the best one
complexities, best_algorithm = test_algorithms(algorithms, test_data)

for func , name in zip(funcs[1:],complexity_names[1:]):
new_fit, _ , _ = leastSquares(x,y,func)
if new_fit < best_fit:
best_fit = new_fit
best_name = name
print("Complexities of each algorithm:")
for algo, complexity in complexities.items():
print(f"{algo}: {complexity}")

return best_name
print(f"The best algorithm is: {best_algorithm}")

#TODO: add the code to generate random data for testing
#TODO: add the logic to test algorithms directly and get the best one from them using complexity comparion
36 changes: 36 additions & 0 deletions sort_algos.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,39 @@ def insertion_sort_exchange(arr):
results = []

# TODO: Complete the benchmark code
for func in funcs:
func_results = {
"name": func.__name__,
"random": [],
"sorted": [],
"inverse_sorted": []
}

# Run the experiment for each array type
for arr_type, arrays in zip(["random", "sorted", "inverse_sorted"], [random_arrays, sorted_arrays, inverse_sorted_arrays]):
for i, arr in enumerate(arrays):
comparisons, swaps = 0, 0
# Run the experiment multiple times
for _ in range(nbr_experiments):
arr_copy = arr.copy() # Make sure the array is not modified between experiments
c, s = func(arr_copy)
comparisons += c
swaps += s

# Average the results
comparisons_avg = comparisons / nbr_experiments
swaps_avg = swaps / nbr_experiments

func_results[arr_type].append((comparisons_avg, swaps_avg))

# Append the results of this function to the main results list
results.append(func_results)

# Print the results
for result in results:
print(f"Sorting function: {result['name']}")
for arr_type in ["random", "sorted", "inverse_sorted"]:
print(f" {arr_type.capitalize()} arrays:")
for i, (comp, swap) in enumerate(result[arr_type]):
print(f" Length {lenghts[i]} - Comparisons: {comp:.2f}, Swaps: {swap:.2f}")