why does the sort(with key) function not work as intended?

Viewed 114
# A function that returns the frequency of each value:
def myFunc(e):
     return cars.count(e)

cars = ['Ford', 'Ford', 'Ford', 'Mitsubishi','Mitsubishi', 'BMW', 'VW']

cars.sort(key=myFunc) 

print(cars)

Output:

['Ford', 'Ford', 'Ford', 'Mitsubishi', 'Mitsubishi', 'BMW', 'VW']

What I expect:

['BMW', 'VM', 'Mitsubishi', 'Mitsubishi', 'Ford', 'Ford', 'Ford']

Counts:

Ford - 3
Mitsubishi - 2
BMW - 1
VM - 1

It should sort in ascending order of count in the list.

2 Answers

The problem is that you are using cars inside the key function, but .sort is in-place. This causes cars to be unreliable in intermediate calls to the key function.

We can see the problem if we print cars inside the key function:

def myFunc(e):
    print(cars)
    return cars.count(e)


cars = ['Ford', 'Ford', 'Ford', 'Mitsubishi', 'Mitsubishi', 'BMW', 'VW']

cars.sort(key=myFunc)

This outputs

[]
[]
[]
[]
[]
[]
[]

so cars.count will return 0 regardless what element is passed and the list will retain its original order.

Use sorted(...) which is not in-place:

def myFunc(e):
    return cars.count(e)


cars = ['Ford', 'Ford', 'Ford', 'Mitsubishi', 'Mitsubishi', 'BMW', 'VW']

cars = sorted(cars, key=myFunc)

print(cars)

This outputs

['BMW', 'VW', 'Mitsubishi', 'Mitsubishi', 'Ford', 'Ford', 'Ford']

As a side-note, in this case you can use cars.count directly, without defining the wrapper function:

cars = sorted(cars, key=cars.count)

This issue is coming from the fact that you reference cars in the function while modifying it.

This doesn't happen if you get a copy:

def myFunc(e):
     return cars.count(e)

cars = ['Ford', 'Ford', 'Ford', 'Mitsubishi','Mitsubishi', 'BMW', 'VW']

cars2 = cars.copy()

cars2.sort(key=myFunc) 

print(cars2)
# ['BMW', 'VW', 'Mitsubishi', 'Mitsubishi', 'Ford', 'Ford', 'Ford']

That said, this approach is not efficient as you need to read again the whole list for each element.

use a counter instead:

from collections import Counter

cars = ['Ford', 'Ford', 'Ford', 'Mitsubishi','Mitsubishi', 'BMW', 'VW']
c = Counter(cars)

cars.sort(key=c.get)

print(cars)
# ['BMW', 'VW', 'Mitsubishi', 'Mitsubishi', 'Ford', 'Ford', 'Ford']
Related