Что было бы наиболее элегантным / питоническим способом достижения: «если x% от общего значения в списке больше, чем y, вернуть true». В настоящее время я реализовал функцию:

def check(listItems, val):
   '''A method to check all elements of a list against a given value.
   Returns true if all items of list are greater than value.'''
   return all(x>val for x in listItems)

Но для моего варианта использования ожидание этого конкретного условия довольно дорого и несколько бесполезно. Я хотел бы продолжить, если ~ 80% элементов в списке больше, чем заданное значение. Один из подходов, на мой взгляд, состоит в том, чтобы отсортировать список в порядке убывания, создать другой список и скопировать 80% элементов списка в новый список, а также запустить функцию для этого нового списка. Тем не менее, я надеюсь, что должен быть более элегантный способ сделать это. Какие-либо предложения?

4
Khizar Amin 9 Июл 2019 в 18:55

5 ответов

Лучший ответ

Похоже, вы имеете дело с длинными списками, поэтому это дорого. Если было бы хорошо, если бы вы могли выйти рано, как только будет выполнено условие. any() сделает это, но вы должны избегать чтения всего списка перед передачей его any(). Один из вариантов может состоять в том, чтобы использовать itertools.accumulate для хранения промежуточного итогового значения True значений и передавать его любому. Что-то вроде:

from itertools import accumulate

a = [1, 2, 2, 3, 4, 2, 4, 1, 1, 1]

# true if 50% are greater than 1
goal = .5 * len(a) # at least 5 out of 10   
any( x > goal for x in accumulate(n > 1 for n in a))

accumulate не нужно будет читать весь список - он просто начнет передавать число истинных значений, видимых до этого момента. any должен замкнуть накоротко, как только он найдет истинное значение, которое в вышеприведенном случае имеет индекс 5.

7
Mark Meyer 9 Июл 2019 в 16:21

Как насчет этого:

def check(listItems, val, threshold=0.8):
    return sum(x > val for x in listItems) > len(listItems) * threshold

В нем говорится: check равен True, если более threshold% (по умолчанию 0,80) элементов в listItems больше val.

2
Óscar López 9 Июл 2019 в 16:08

Я не хочу брать на себя ответственность за ответ Марка Мейера, поскольку он придумал концепцию использования накопления и того, что они более питонны / читабельны, но если вы ищете «самый быстрый» подход, измените его подход. с использованием map против использования пониманий быстрее.

any(map(goal.__le__, accumulate(map(val.__lt__, listItems))))

Просто чтобы проверить:

from timeit import timeit
from itertools import accumulate

def check1(listItems, val):
    goal = len(listItems)*0.8
    return any(x > goal for x in accumulate(n > val for n in listItems))

def check2(listItems, val):
    goal = len(listItems)*0.8
    return any(map(goal.__le__, accumulate(map(val.__lt__, listItems))))

items = [1, 2, 2, 3, 4, 2, 4, 1, 1, 1]

for t in (check1, check2):
    print(timeit(lambda: t(items, 1)))

Результаты:

3.2596251670038328
2.0594907909980975
0
Jab 9 Июл 2019 в 17:25

Проверьте каждый элемент по порядку.

  • Если вы достигли точки, когда вы удовлетворены, тогда верните Истину рано.

  • Если вы достигнете точки, в которой вы никогда не будете удовлетворены, даже если каждый будущий предмет пройдет тест, верните False раньше.

  • В противном случае продолжайте (если последующие элементы помогут вам удовлетворить требование).

Это та же идея, что и у ФатихАкичи в комментариях выше, но с дальнейшей оптимизацией.

def check(list_items, ratio, val):
    passing = 0
    satisfied = ratio * len(list_items)
    for index, item in enumerate(list_items):
        if item > val:
            passing += 1
        if passing >= satisfied:
            return True
        remaining_items = len(list_items) - index - 1
        if passing + remaining_items < satisfied:
            return False
1
Zev Chonoles 9 Июл 2019 в 16:24

Вы можете использовать filter для этого. Безусловно, это самый быстрый метод. Обратитесь к моему другому ответу, так как это быстрее, чем методы в этом.

def check(listItems, val, goal=0.8):
    return len((*filter(val.__lt__, listItems),)) >= len(listItems) * goal

Проверенное время результата для этого побежало вместе с методами в моем другом вопросе:

1.684135717988247
2
Jab 10 Июл 2019 в 14:59