Как избавиться от if'ов

Рейтинг: 0Ответов: 6Опубликовано: 13.04.2023

Задача: отсортировать список, в котором хранятся словари. У словарей +- одинаковые ключи и значения (пример ниже).

Проблема в том, что при увеличении количества принимаемых аргументов, по которым фильтруем словари, увеличивается количество блоков if [Аргумент функции] not None. Как этого можно избежать? И какой самый оптимальный вариант решения этой задачи?

arr = [
    {'title': 'Dom kva', 'price':2000}, # ключ-значение может быть больше 
    {'title': 'dom2 kva', 'price':3000},
    {'title': 'dom3', 'price':3555},
    {'title': 'dom4','price':4000},
    {'title':'dom6 kva','price':3540}
]

def sort_price(array:list, high :int=None , low: int=None, text : str=None) -> list:
    sort_arr = []
    for item in array:
        if low is not None:
            if item['price'] < low:
                continue
        if high is not None:
            if item['price'] > high:
                continue
        if text is not None:
            if text not in item['title']:
                continue
        sort_arr.append(item)
    return sort_arr

print(sort_price(arr,3999,2000,'kva'))  # выводит в консоль [{'title': 'Dom kva', 'price': 2000}, {'title': 'dom2 kva', 'price': 3000}, {'title': 'dom6 kva', 'price': 3540}]

Ответы

▲ 4Принят

Можно все условия объединить через операторы and и or

arr = [
    {'title': 'Dom kva', 'price': 2000},  # ключ-значение может быть больше
    {'title': 'dom2 kva', 'price': 3000},
    {'title': 'dom3', 'price': 3555},
    {'title': 'dom4', 'price': 4000},
    {'title': 'dom6 kva', 'price': 3540}
]


def sort_price(array: list, high: int = None, low: int = None, text: str = None) -> list:
    sort_arr = []
    for item in array:
        if not (low is not None and item['price'] < low
                or high is not None and item['price'] > high
                or text is not None and text not in item['title']):
            sort_arr.append(item)
    return sort_arr


print(sort_price(arr, 3999, 2000,'kva'))
[{'title': 'Dom kva', 'price': 2000}, {'title': 'dom2 kva', 'price': 3000}, {'title': 'dom6 kva', 'price': 3540}]
▲ 2

Можно использовать по умолчанию верхнюю границу как плюс бесконечность, а нижнюю минус бесконечность, также пустая строка есть в любой строке, потом два сравнения можно объединить в цепочку сравнений (a <= b <= c == a <= b and b <= c).

import math

def sort_price(array: list[dict], high: int | float = math.inf, low: int | float = -math.inf, text: str = '') -> list[dict]:
    sort_array = []
    for item in array:
        if low <= item['price'] <= high and text in item['title']:
            sort_array.append(item)
    return sort_array

print(sort_price(array, 3999, 2000, 'kva'))
[{'title': 'Dom kva', 'price': 2000}, {'title': 'dom2 kva', 'price': 3000}, {'title': 'dom6 kva', 'price': 3540}]

P.S.: -math.inf, math.inf можно заменить на float('-inf'), float('inf').

P.P.S.: Вариант в одну строчку:

def sort_price(array: list[dict], high: int | float = math.inf, low: int | float = -math.inf, text: str = '') -> list[dict]:
    return [item for item in array if low <= item['price'] <= high and text in item['title']]
▲ 1

Можно с ифами, но по другому:

def sort_price(array: list, high: int = None, low: int = None, text: str = None) -> list:
    sort_arr = array
    if low is not None:
        sort_arr = (item for item in sort_arr if item['price'] >= low)
    if high is not None:
        sort_arr = (item for item in sort_arr if item['price'] <= high)
    if text is not None:
        sort_arr = (item for item in sort_arr if text in item['title'])
    return list(sort_arr)
▲ 1

Не надо избавляться от if-ов. Достаточно вынести их из цикла.

Каждое условие оформляется как отдельный предикат. Две строки на условие:

# это не сортировка!
# аннотации типов исправлены
def select_price(array: list, low: int|None = None, high: int|None = None, text: str|None = None) -> list:
    # список предикатов-фильтров
    preds = []
    if low is not None:
        preds.append(lambda item: low <= item['price'])
    if high is not None:
        preds.append(lambda item: item['price'] <= high)
    if text is not None:
        preds.append(lambda item: text in item['title'])

    # лучше написать так:
    # return [item for item in array if all(p(item) for p in preds)]
     
    # но если вы предпочитаете циклы, то так:
    selected = []
    for item in array:
        # если все предикаты согласны, то ...
        if all(p(item) for p in preds):
            selected.append(item)
    return selected


arr = [
    {'title': 'Dom kva', 'price':2000},
    {'title': 'dom2 kva', 'price':3000},
    {'title': 'dom3', 'price':3555},
    {'title': 'dom4','price':4000},
    {'title':'dom6 kva','price':3540}
]

# параметры со значениями по-умолчанию полезно именовать в вызове
print(select_price(arr, low=2000, high=3999, text='kva'))

P.S. Если для text установить пустую строку по-умолчанию, можно упростить аннотацию и убрать одно условие.

▲ 1

Ага. Нужно пояснить мой коммент. Не знаю причем тут сортировка, как по мне метод занимается фильтрацией. И вообще должен выбирать по критериям, а не удалять. Ну да ладно.

я говорил про такое (если нужен переменный набор правил, добавлять их потом и вообще все красиво). А ведь предикаты могут быть и полноценными классами, но у нас же тут не java

arr = [
    {'title': 'Dom kva', 'price': 2000},  # ключ-значение может быть больше
    {'title': 'dom2 kva', 'price': 3000},
    {'title': 'dom3', 'price': 3555},
    {'title': 'dom4', 'price': 4000},
    {'title': 'dom6 kva', 'price': 3540}
]

from typing import Callable, Dict, Iterable, TypeAlias, Any

Predicate: TypeAlias = Callable[[Dict[str, Any]], bool]


def lowprice(value: int) -> Predicate:
    return lambda o: o['price'] < value


def highprice(value: int) -> Predicate:
    return lambda o: o['price'] > value


def notintitle(text: str) -> Predicate:
    return lambda o: text not in o['title']


def sort_price(array: list, rules: Iterable[Predicate]) -> list:
    sort_arr = []
    for item in array:
        for rule in rules:
            if rule(item):
                break
        else:
            sort_arr.append(item)
    return sort_arr


result = sort_price(arr, rules=(highprice(3999), lowprice(200), notintitle('kva')))
print(result)

Конечно можно было бы сделать и словарь Dict[str, Callable], но с методами IDE и метод подскажет, и аргументы проверит.

▲ 0

Осмелюсь предложить свой вариант, хотя он мне самому не очень нравится. Поддерживаю идею vitidev, если нужна динамика, то без списков или словарей не обойтись. Внимание!!! Данный код потенциально подвержен уязвимости посредством текстовой подстановки.

def sort_price(array: list, high: int = None, low: int = None, text: str = None) -> list:
    filter = eval(
        f"lambda x: (x['price'] >= {low} if bool({low}) else True)"
        f" and (x['price'] <= {high} if bool({high}) else True)"
        f" and (True if '{text}' == 'None' else '{text}' in x['title'])"
    )
    return list(item for item in array if filter(item))


sort_price(arr, 3900, 3000, "kva")  # [{'title': 'dom2 kva', 'price': 3000}, {'title': 'dom6 kva', 'price': 3540}]