Пифоническое использование операторов if для неравенств, примененных к списку - PullRequest
3 голосов
/ 09 октября 2019

Учитывая следующие списки в Python:

l1 = [0,1000,5000,10000,20000,30000,40000,50000] #8 values, 7 intervals
v1 = [1,2,3,4,5,6,7] #7 values
v2 = [a,b,c,d,e,f,g] #7 letters

Я хочу проверить, содержится ли число в каком-либо интервале, и вернуть другое значение в зависимости от того, какой это интервал.

Пример:

1. My test value is 1111
2. It belongs to the second interval: 1000 < 1111 < 5000
3. Hence I need to return b

Я бы решил проблему следующим образом:

  1. Создание фрагментов l1
  2. Итерация по каждому фрагменту
  3. Запись одногооператор if для каждого чанка
  4. вернуть букву, соответствующую правильному чанку

Я могу создать ее чеки, просматривая каждую последовательную пару чисел:

def chunker(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq)))

for group in chunker(l1, 2):
   print(group)

Возвращает:

[0, 1000]
[1000, 5000]
[5000, 10000]
[10000, 20000]
[20000, 30000]
[30000, 40000]
[40000, 50000]
[50000]

Мои вопросы:

  1. Есть ли Pythonic способ написать эти операторы if вместо одного для каждого чанка? Что, если у меня 1000 чанков?
  2. Если есть, как поступить с последним чанком, не создавая для него особого случая?

Ответы [ 6 ]

4 голосов
/ 09 октября 2019

Вы можете просто использовать генератор, чтобы получить результат:

l1 = [0,1000,5000,10000,20000,30000,40000,50000]
v2 = ['a','b','c','d','e','f','g']

value = 1111

result = next((v for v, i, j in zip(v2, l1[:-1], l1[1:]) if value in range(i, j)), None)

Вывод:

>>> result
'b'

Добавив значение по умолчанию None в качестве второго аргумента next(),Вы также можете обрабатывать случаи, когда они не найдены:

value = -50

result = next((v for v, i, j in zip(v2, l1[:-1], l1[1:]) if value in range(i, j)), None)

>>> result
# None

Если вы просто хотели индекс, вместо него можно использовать enumerate:

result = next((v for v, (i, j) in enumerate(zip(l1[:-1], l1[1:])) if value in range(i, j)), None)

>>> result
# 1

Объяснение:

Строка результата состоит из нескольких частей:

next(iterator [, default])

Это функция для получения следующего элемента в iterator, переданном в аргументе. default служит для возврата значения по умолчанию, если встречается StopIteration. Рассматриваемый iterator является здесь генератором (для ясности разбит):

(
  v                                  # point 4
    for v, i, j                      # point 2
      in zip(v2, l1[:-1], l1[1:])    # point 1
    if value in range(i, j)          # point 3
)
  1. Функция zip объединяет переданные lists, так что v2[0], l1[:-1][0] и l1[1:][0] формируются в кортеж ('a', 0, 1000) и т. Д. Для каждого индекса.

  2. for v, i, j служит для извлечения элементов в кортеже.

  3. if value in range(i, j) служит для проверки того, что 1111 находится в диапазоне между range(0, 1000).

  4. Если это совпадение, возвращается v. Если нет, переходите к следующей итерации.

4 голосов
/ 09 октября 2019

Вы можете zip перечислить l1 и l1[1:], которые сделают интервалы нужным вам образом. А для некоторых дополнительных указателей вы можете включить в них бинарный поиск, так как интервалы отсортированы, и я оставлю его для оптимизации. Текущее время выполнения этого алгоритма O(n), вы можете уменьшить время выполнения до O(log(n)).

from __future__ import print_function
l1 = [0,1000,5000,10000,20000,30000,40000,50000]
v2 = ['a','b','c','d','e','f','g']
element = 1111
interval = []
letter = None
for index,(left,right) in enumerate(zip(l1,l1[1:])):
    if left <= element <= right:
        interval = [left,right]
        letter = index
        break
if letter:
    print("answer is ",v2[letter])
    print("interval is" ,interval)
else:
    print("No interval found")

EDIT

Я добавил алгоритм, использующий модуль bisect в python, он производит тот же вывод, но использует двоичный поиск, следовательно, он должен быть быстрее иработает в O(log(n))

from __future__ import print_function
import bisect
li = [0,1000,5000,10000,20000,30000,40000,50000]
v2 = ['a','b','c','d','e','f','g']
element = 1111
index = bisect.bisect(li, element)
if index == 0 or index == len(li):
    print("No interval found")
else:
    print("answer is",v2[index - 1])
    print("interval is",li[index - 1], li[index])

ВЫХОД

answer is  b
interval is [1000, 5000]
3 голосов
/ 09 октября 2019

Хотя другой ответ полезен, но почти все они находят интервал, а не желают выводить из v2

l1 = [0,1000,5000,10000,20000,30000,40000,50000]
v2 = ['a','b','c','d','e','f','g']
element = 1111
def get_interval(l1):
    for index, left, right in zip(range(len(l1)), l1, l1[1:]):
        if left <= element <= right:
            return v2[index]

>>> print("answer is:" ,get_interval(element))
>>> answer is: b
3 голосов
/ 09 октября 2019

Я считаю, что вам нужно получить соответствующий индекс интервала и использовать его для запроса v2. Это должно сделать это:

l1 = [0,1000,5000,10000,20000,30000,40000,50000] #8 values, 7 intervals
v2 = ['a','b','c','d','e','f','g'] #7 letters


def intervals(l):
    for i in range(len(l)-1):
        yield i, l[i:i+2]


def interval_value(val, interval_list, value_list):
    for i, interval in intervals(interval_list):
        if interval[0] <= val <= interval[1]:
            return value_list[i]


print(interval_value(1111, l1, v2))
print(interval_value(0, l1, v2))
print(interval_value(51000, l1, v2))
print(interval_value(40000, l1, v2))

Вывод:

b
a
None
f

Вам не нужны значения v1 - вы можете работать с индексом на v2 напрямую

3 голосов
/ 09 октября 2019

Прямо из книги рецептов itertools:

def get_thing(value):

    def pairwise(iterable):
        from itertools import tee
        a, b = tee(iterable)
        next(b, None)
        return zip(a, b)

    interval_ranges = [
        0,
        100,
        500,
        1000
    ]

    # There are four interval ranges, so three intervals.
    things = [
        "A", # 0-100
        "B", # 100-500
        "C" # 500-1000
    ]

    for (begin, end), thing in zip(pairwise(interval_ranges), things):
        if begin <= value < end: # modify this to suit your needs. Is the range inclusive/exclusive?
            return thing
    return None


def main():

    thing = get_thing(400)
    print(thing)

    return 0


if __name__ == "__main__":
    import sys
    sys.exit(main())

Вывод:

B
1 голос
/ 09 октября 2019

Это работает только с кусками len 2.

l1 = [0,1000,5000,10000,20000,30000,40000,50000] #8 values, 7 intervals
# v1 = [1,2,3,4,5,6,7] #7 values
v2 = ['a', 'b', 'c', 'd', 'e', 'f', 'g']  # 7 letters

def chunker(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq)))

def chunk_with_value(list_of_chunks, value):
    """returns the chunk if the value is inside the range"""
    for chunk in list_of_chunks:
        if chunk[0] < value < chunk[1]:
            return chunk

def chunk_to_letter(value_list, letter_list, chunk):
    """returns the letter based on the index of the first chunk element"""
    for i, value in enumerate(l1):
        if value == chunk[0]:
            return v2[i]


chunks = chunker(l1, 2)
chunk = chunk_with_value(chunks, 1111)
print(chunk)  # [1000, 5000]
print(chunk_to_letter(l1, v2, chunk))  # b
...