0

I want to write a decorator that will count the number of swaps in a sorting algorithm. I created a swap function to swap numbers and a decorator that counts each call to this function. The issues I've encountered are as follows: when I count the number of calls for multiple algorithms, it counts the "swaps" for each of them and sums them up. Another issue is that each "swap" is printed in the console. Below, I'm including the decorator, swap and a sample algorithm.

Decorator:

def swapCounter(func):
    def counter(*args, **kwargs):
        counter.calls += 1
        print(f"Swaps {counter.calls}")
        return func(*args, **kwargs)
    counter.calls = 0
    return counter

Swap:

def swap(arr, i, j):
        arr[i], arr[j] = arr[j], arr[i]

Sample sorting algorithm:

def bubbleSort(arr):
    for i in range(len(arr)):
        for j in range(0, len(arr)-i-1):
            if arr[j] > arr[j+1]:
                swap(arr, j, j+1)

    return arr

I tried adding flags in the decorator and in the swap function. In swap, the flag would be set at the end, allowing the decorator to reset at the right moment. However, this didn’t yield any results: AttributeError: 'function' object has no attribute 'calls'

def swapCounter(func):
    def counter(*args, **kwargs):
        counter.calls+=1
        if kwargs.get("flag")==True:
            print(f"Swaps {counter.calls}")
            counter.calls = 0
            return func(*args, **kwargs)
    return counter
def swap(arr=None, i=None, j=None, flag=None):
        arr[i], arr[j] = arr[j], arr[i]
def bubbleSort(arr):
    for i in range(len(arr)):
        for j in range(0, len(arr)-i-1):
            if arr[j] > arr[j+1]:
                swap(arr, j, j+1)

    swap(True)
2
  • 1
    you haven't shown how you're using the decorator. Commented Nov 6, 2024 at 16:31
  • If you don't want each swap to be printed, why do you have print(f"Swaps {counter.calls}") in the decorator? If you just want the final count, you need to do that outside the function. Commented Nov 6, 2024 at 16:32

4 Answers 4

1

Just for fun, using the lru_cache decorator, which lets you clear the counter.

from functools import lru_cache

@lru_cache(0)
def swap(arr, i, j):
        arr[i], arr[j] = arr[j], arr[i]

def bubbleSort(arr):
    for i in range(len(arr)):
        for j in range(0, len(arr)-i-1):
            if arr[j] > arr[j+1]:
                swap(arr, j, j+1)
    return arr

for arr in [3,2,1], [4,3,2,1]:
    bubbleSort(arr)
    print(swap.cache_info().misses, 'swaps')
    swap.cache_clear()

Output (Attempt This Online!):

3 swaps
6 swaps
Sign up to request clarification or add additional context in comments.

Comments

0

You have a few minor problems in your implementation. Firstly, in swapCounter you would need to set counter.calls = 0 outside of the counter function so that the attribute is defined from outside of the scope of the function. Also, I think your method of resetting the counter can be done a bit more elegantly if we remove the flag from the decorator and instead attach a reset method to the decorator function.

Take a look at this swapCounter function:

def swapCounter(func):

    def counter(*args, **kwargs):
        # Increment the swap counter on each call
        counter.calls += 1
        # Only call the original function; no printing here
        return func(*args, **kwargs)

    # Attribute to hold the number of swap calls
    counter.calls = 0

    # Method to reset and retrieve count for a new sorting run
    def reset_count():
        swap_count = counter.calls
        counter.calls = 0
        return swap_count

    # Attach the reset method to the decorator function
    counter.reset_count = reset_count
    return counter

Now, we can simply add the decorator to the swap function as follows:

@swapCounter
def swap(arr, i, j):
    arr[i], arr[j] = arr[j], arr[i]

And then your sort function would look something like this:

def bubbleSort(arr):
    for i in range(len(arr)):
        for j in range(0, len(arr) - i - 1):
            if arr[j] > arr[j + 1]:
                swap(arr, j, j + 1)
    # Print the total number of swaps for this run
    print(f"Total Swaps in Bubble Sort: {swap.reset_count()}")
    return arr

Also, note that the final line return arr is a bit redundant as your swap function modifies the array passed to the function so for example:

arr1 = [5, 3, 8, 4, 2]
arr2 = bubbleSort(arr1)
assert arr1 == arr2 # This doesn't give an error as the arrays are now the same

Comments

0

You could simply (re)define the swap function for each sorting algorithm. Then you have a fresh counter for each.

for sort in bubbleSort, insertionSort, selectionSort:

    arr = ...

    @swapCounter
    def swap(arr=None, i=None, j=None, flag=None):
        arr[i], arr[j] = arr[j], arr[i]

    sort(arr)

    print(swap.calls)

Comments

0

You could set the count as 0 in the begining of the function:

def bubbleSort(arr):
    swap.calls = 0

And the decorator on the swap

@swapCount
def swap(arr):
    pass

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.