How to sort complex objects with custom criteria in Python
Tags: python, tutorial, programming, sorting
Published: 2023-05-27 16:22:30

In most cases, sorting is a simple task but there are occasions when the data is a bit complicated and python provides elegant way to handle those scenarios. So we will look at a complex scenario in this article

## Object structure

### With class definition

```python
class Laptop:
    def __init__(self, cpu, ram, ssd) -> None:
    self.cpu = cpu
    self.ram = ram
    self.ssd = ssd

A = Laptop("Ryzen 7", 8,  256)
B = Laptop("Ryzen 5", 8,  512)
C = Laptop("Ryzen 7", 16, 128)
D = Laptop("Ryzen 5", 16, 128)

arr = [A,B,C,D]
```

### As list items

```python
A = [ "Ryzen 7", 8, 256 ]
B = [ "Ryzen 5", 8, 512 ]
C = [ "Ryzen 7", 16, 128 ]
D = [ "Ryzen 5", 16, 128 ]

arr = [A,B,C,D]
```

## Table of Contents

- [Sort using key parameter](#sort-using-key-parameter)
- [Operator overloading](#operator-overloading)
- [Comparator function](#comparator-function)
- [The smarter way to solve this problem](#the-smarter-way-to-solve-this-problem)
- [Conclusion](#conclusion)


## Sort using key parameter

Lets say, our priority is in this order, `cpu > ram > ssd`

```python
# As class objects
arr.sort(key=lambda x:(x.cpu,x.ram, x.ssd), reverse=True)

# As list items
arr.sort(key=lambda x:(x[0], x[1], x[2]), reverse=True)
```

The result is,

```
Ryzen 7, 16, 128
Ryzen 7, 8, 256
Ryzen 5, 16, 128
Ryzen 5, 8, 512
```

For simpler cases, when you have only one criteria, you don't need to use tuples

```python
arr.sort(key=lambda x:x.cpu, reverse=True)
```

## Operator overloading

Lets make the scenario more complex by introducing Intel,

```python
E = Laptop("Intel i7", 16, 512)

arr = [A,B,C,D,E]
```

If we don't change anything the result will be,

```
Ryzen 7, 16, 128
Ryzen 7, 8, 256
Ryzen 5, 16, 128
Ryzen 5, 8, 512
Intel i7, 16, 512
```

-   Which may not be what we want, we were getting results as expected before because we assumed that `Ryzen 7 > Ryzen 5` which is carried out by the "<" operator of string

-   For the sake of demonstration lets define the precedence in this way,
    `Ryzen 7 > Intel i7 > Ryzen5`

Here is one way to achieve this result using operator overloading

```python
class Laptop:
    def __init__(self, cpu, ram, ssd) -> None:
        self.cpu = cpu
        self.ram = ram
        self.ssd = ssd

    def __lt__(a, b):
        brand_a, model_a = a.cpu.split(" ")
        brand_b, model_b = b.cpu.split(" ")

        if brand_a == brand_b:
            if model_a != model_b:
                return model_a < model_b
        else:
            if brand_a == "Intel":
                return b.cpu == "Ryzen 7"
            elif brand_b == "Intel":
                return a.cpu == "Ryzen 5"

        if a.ram != b.ram:
            return a.ram < b.ram

        return a.ssd < b.ssd
```

In this function, returning 0 means a is smaller, if 1 is returned then b is smaller

When the "<" operator is defined, you can sort by simply calling

```python
arr.sort(reverse=True)
```

## Comparator function

```python
from functools import cmp_to_key

def comparator(a, b):
    return a.ram - b.ram

arr.sort(key=cmp_to_key(comparator), reverse=True)
```

You can write logic of similar complexity by using comparator function

But in this method, returning -1 or any negative number means a is smaller, if positive number is returned than b is smaller. If 0 is returned then they have the same priority and no swapping will be done

-   The benefit of using comparator function is that you don't overload the class operators and you have the option to use multiple comparators for different use cases.
-   If you have already overloaded the "<" operator but a scenario arises where your criteria is a bit different then comparator function is what you may need

## The smarter way to solve this problem

Use a dictionary to define the priority of the cpu models,

```python
mp = {
    "Ryzen 5"  : 0,
    "Intel i7" : 1,
    "Ryzen 7"  : 2
}
```

Then the logic becomes simpler,

```python
def __lt__(a, b):
    if a.cpu != b.cpu:
        return mp[a.cpu] < mp[b.cpu]

    if a.ram != b.ram:
        return a.ram < b.ram

    return a.ssd < b.ssd
```

To use comparator function, just replace the less than ("<") operators with minus("-") operator

You may have already guessed that you can make it even simpler without using operator overloading

```python
arr.sort(key=lambda x:(mp[x.cpu], x.ram, x.ssd),reverse=True)
```

This will be the result,

```
Ryzen 7, 16, 128
Ryzen 7, 8, 256
Intel i7, 16, 512
Ryzen 5, 16, 128
Ryzen 5, 8, 512
```

## Conclusion

All three ways may have their places but if you can perform the task by using lambda in key parameter than you should stick to that

Find me on
💻 [Github](https://github.com/renzhamin)
🔘 [LinkedIn](https://www.linkedin.com/in/renzhamin)
⭕ [Twitter](https://twitter.com/renzhamin)