Pythonic способ реализовать три одинаковых операторов целочисленного диапазона? - PullRequest
2 голосов
/ 12 июня 2009

Я работаю над круговой проблемой. В этой задаче у нас есть объекты, которые помещены в кольцо размером MAX, и которым назначены идентификаторы от (0 до MAX-1).

У меня есть три простые функции для проверки включений в диапазоне. inRange (i, j, k) проверяет, находится ли i в круговом интервале [j, k [(мнемоника равна i inRange (j, k) ). И у меня то же самое для диапазонов] j, k [и] j, k].

Код в этих трех методах выглядит дублированным от одного метода к другому:

def inRange(i,j,k):
    """
    Returns True if i in [j, k[
    * 0 <= i, j, k < MAX
    * no order is assumed between j and k: we can have k < j
    """
    if j <= k:
        return j <= i < k
    # j > k :
    return j <= i or i < k

def inStrictRange(i,j,k):
    """
    Returns True if i in ]j, k[
    * 0 <= i, j, k < MAX
    * no order is assumed between j and k: we can have k < j
    """
    if j <= k:
        return j < i < k
    # j > k :
    return j < i or i < k

def inRange2(i,j,k):
    """
    Returns True if i in ]j, k]
    * 0 <= i, j, k < MAX
    * no order is assumed between j and k: we can have k < j
    """
    if j <= k:
        return j < i <= k
    # j > k :
    return j < i or i <= k

Знаете ли вы какой-нибудь более чистый способ реализации этих трех методов? Ведь меняются только операторы?!

Подумав о лучшем решении, я придумал:

from operator import lt, le
def _compare(i,j,k, op1, op2):
    if j <= k:
        return op1(j,i) and op2(i,k)
    return op1(j,i) or op2(i,k)

def inRange(i,j,k):
    return _compare(i,j,k, le, lt)
def inStrictRange(i,j,k):
    return _compare(i,j,k, lt, lt)
def inRange2(i,j,k):
    return _compare(i,j,k, lt, le)

Что лучше? Можете ли вы придумать что-нибудь более интуитивное? Короче говоря, Каким будет Pythonic способ написать эти три оператора?

Кроме того, я ненавижу имена inRange, inStrictRange, inRange2, но не могу вспомнить кристально чистые имена. Есть идеи?

Спасибо.

Ответы [ 7 ]

7 голосов
/ 12 июня 2009

Два Дзен Питона принципы, которые приходят на ум:

  • Простое лучше, чем сложное.
  • Должен быть один - и предпочтительно только один - очевидный способ сделать это.

range

Встроенная функция Python range(start, end) создает список от start до end. 1 Первый элемент этого списка - start, а последний элемент - end - 1 .

Нет функции range_strict или inclusive_range. Мне было очень неловко, когда я начинал работать на Python. («Я просто хочу список от a до b включительно! Насколько это сложно, Гвидо?») Однако соглашение, используемое при вызове функции range, было простым и легко запоминающимся, а отсутствие нескольких Функции позволили легко запомнить, как каждый раз генерировать диапазон.

Рекомендация

Как вы, наверное, догадались, я рекомендую создать только функцию для проверки того, находится ли i в диапазоне [ j , k ). На самом деле, я рекомендую оставить только вашу существующую функцию inRange.

(Поскольку в вашем вопросе конкретно упоминается Pythonicity, я бы порекомендовал вам назвать эту функцию как in_range, чтобы лучше соответствовать Python Style Guide .)

Обоснование

Почему это хорошая идея?

  • Эту функцию легко понять. Научиться пользоваться им очень легко.

    Конечно, то же самое можно сказать о каждой из трех ваших стартовых функций. Пока все хорошо.

  • Существует только одна функция для изучения. Не существует трех функций с обязательно одинаковыми именами.

    Учитывая одинаковые имена и поведение ваших трех функций, вполне возможно, что в какой-то момент вы будете использовать неправильную функцию. Это усугубляется тем фактом, что функции возвращают одно и то же значение, за исключением крайних случаев, что может привести к трудно обнаруживаемой ошибке «один за другим». Делая доступной только одну функцию, вы знаете, что не допустите такой ошибки.

  • Функция легко редактируется.

    Маловероятно, что вам когда-либо понадобится отлаживать или редактировать такой простой фрагмент кода. Однако, если вам нужно сделать это, вам нужно только отредактировать эту функцию. С вашими первоначальными тремя функциями вы должны сделать одно и то же редактирование в трех местах. С вашим измененным кодом в вашем ответе на код оператор становится немного менее интуитивным.

  • «Размер» диапазона очевиден.

    Для данного кольца, где вы будете использовать inRange(i, j, k), очевидно, сколько элементов будет охвачено диапазоном [ j , k ). Вот это в коде.

    if j <= k:
        size = k - j
    if j > k:
        size = k - j + MAX
    

    Итак, поэтому

    size = (k - j) % MAX
    

Предостережения

Я подхожу к этой проблеме с совершенно общей точки зрения, например, с точки зрения человека, пишущего функцию для публично выпущенной библиотеки. Поскольку я не знаю вашу проблемную область, я не могу сказать, является ли это практическим решением.

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


1 : На самом деле это range([start], end, [step]). Я верю, что вы понимаете, что я имею в виду.

5 голосов
/ 12 июня 2009

Pythonic способ сделать это состоит в том, чтобы выбрать удобочитаемость, и для этого сохранить 3 метода, как они были в начале.

Не похоже, что они ОГРОМНЫЕ методы, или их тысячи, или вам придется их динамически генерировать.

4 голосов
/ 12 июня 2009

Нет функций высшего порядка, но меньше кода, даже с посторонними else.

def exclusive(i, j, k):
    if j <= k:
        return j < i < k
    else:
        return j < i or i < k

def inclusive_left(i, j, k):
    return i==j or exclusive(i, j, k)

def inclusive_right(i, j, k):
    return i==k or exclusive(i, j, k)

Я на самом деле пытался переключить идентификаторы на n, a, b, но код стал выглядеть менее сплоченным. (Моя точка зрения: совершенствование этого кода не может быть продуктивным использованием времени.)

2 голосов
/ 13 июня 2009

Я, безусловно, согласен с тем, что вам нужна только одна функция, и что в этой функции должен использоваться (Pythonic) полуоткрытый диапазон.

Два предложения:

  1. Используйте значимые имена для аргументов: in_range (x, lo, hi) большой улучшение по сравнению с Стоимость двух нажатий.

  2. Документ о том, что ограничение привет все MAX элементы. Как Уэсли отметил, размер = (к - J)% MAX т.е. размер = (привет - вот)% MAX и, таким образом, 0 <= размер <MAX </em>.

2 голосов
/ 12 июня 2009

Теперь я думаю о чем-то вроде:

def comparator(lop, rop):
    def comp(i, j, k):
        if j <= k:
            return lop(j, i) and rop(i,k)
        return lop(j, i) or rop(i,k)

    return comp

from operator import le, lt

inRange = comparator(le, lt)
inStrictRange = comparator(lt, lt)
inRange2 = comparator(lt, le)

Что выглядит действительно лучше.

1 голос
/ 13 июня 2009

Я бы пошел на шаг дальше, чем Уэсли, подыгрывая идиоме обычного питона 'in range'; я написал бы класс cyclic_range:

import itertools

MAX = 10 # or whatever

class cyclic_range(object):
    def __init__(self, start, stop):
        # mod so you can be a bit sloppy with indices, plus -1 means the last element, as with list indices
        self.start = start % MAX
        self.stop = stop % MAX
    def __len__(self):
        return (self.stop - self.start) % MAX
    def __getitem__(self, i):
        return (self.start + i) % MAX
    def __contains__(self, x):
        if (self.start < self.stop):
            return (x >= self.start) and (x < self.stop)
        else:
            return (x >= self.start) or (x < self.stop)
    def __iter__(self):
        for i in xrange(len(self)):
            yield self[i]
    def __eq__(self, other):
        if (len(self) != len(other)): return False
        for a, b in itertools.izip(self, other):
            if (a != b): return False
        return True
    def __hash__(self):
        return (self.start << 1) + self.stop
    def __str__(self):
        return str(list(self))
    def __repr__(self):
        return "cyclic_range(" + str(self.start) + ", " + str(self.stop) + ")"
    # and whatever other list-like methods you fancy

Затем вы можете написать код:

if (myIndex in cyclic_range(firstNode, stopNode)):
    blah

Чтобы сделать эквивалент inRange. Чтобы сделать inStrictRange, напишите:

if (myIndex in cyclic_range(firstNode + 1, stopNode)):

и делать в диапазоне2:

if (myIndex in cyclic_range(firstNode + 1, stopNode + 1)):

Если вам не нравится делать добавления вручную, как насчет добавления этих методов:

    def strict(self):
        return cyclic_range(self.start + 1, self.stop)
    def right_closed(self):
        return cyclic_range(self.start + 1, self.stop + 1)

А потом делает:

if (myIndex in cyclic_range(firstNode, stopNode).strict()): # inStrictRange
if (myIndex in cyclic_range(firstNode, stopNode).closed_right()): # inRange2

Хотя этот подход, ИМХО, более читабелен, он включает в себя выполнение распределения, а не просто вызов функции, который более дорогой - хотя все еще O (1). Но если вы действительно заботитесь о производительности, вы не будете использовать python!

1 голос
/ 13 июня 2009

Чтобы сделать его более привычным для ваших пользователей, у меня была бы одна основная функция in_range с теми же границами, что и range (). Это значительно облегчает запоминание и имеет другие приятные свойства, как упомянул Уэсли.

def in_range(i, j, k):
    return (j <= i < k) if j <= k else (j <= i or i < k)

Вы, безусловно, можете использовать его один для всех своих вариантов использования, добавив 1 к j и / или k. Если вы обнаружите, что вы часто используете определенную форму, вы можете определить ее в терминах основной:

def exclusive(i, j, k):
    """Excludes both endpoints."""
    return in_range(i, j + 1, k)

def inclusive(i, j, k):
    """Includes both endpoints."""
    return in_range(i, j, k + 1)

def weird(i, j, k):
    """Excludes the left endpoint but includes the right endpoint."""
    return in_range(i, j + 1, k + 1)

Это короче, чем дурачиться с операторами, и также гораздо менее запутанно, чтобы понять. Также обратите внимание, что вы должны использовать подчеркивания вместо camelCase для имен функций в Python.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...