Эффективный метод определения местоположения в сетке (массиве)

Я представляю сетку с 2D-списком в python. Я хотел бы выбрать точку (x, y) в списке и определить ее местоположение... правый край, верхний левый угол, где-то посередине...

В настоящее время я проверяю так:

         # left column, not a corner
         if x == 0 and y != 0 and y != self.dim_y - 1:
             pass
         # right column, not a corner
         elif x == self.dim_x - 1 and y != 0 and y != self.dim_y - 1:
             pass
         # top row, not a corner
         elif y == 0 and x != 0 and x != self.dim_x - 1:
             pass
         # bottom row, not a corner
         elif y == self.dim_y - 1 and x != 0 and x != self.dim_x - 1:
             pass
         # top left corner
         elif x == 0 and y == 0:
             pass
         # top right corner
         elif x == self.dim_x - 1 and y == 0:
             pass
         # bottom left corner
         elif x == 0 and y == self.dim_y - 1:
             pass
         # bottom right corner
         elif x == self.dim_x - 1 and y == self.dim_y - 1:
             pass
         # somewhere in middle; not an edge
         else:
             pass

Где у меня есть какая-то функция, которая что-то делает после определения местоположения

dim_x и dim_y — размеры списка.

Есть ли лучший способ сделать это без такого количества операторов if-else? Что-то эффективное было бы хорошо, так как эта часть логики вызывается пару миллионов раз... это для имитации отжига.

Заранее спасибо. Кроме того, как было бы лучше сформулировать заголовок?


person Nope    schedule 26.09.2009    source источник
comment
Ваше форматирование потеряно, пожалуйста, отредактируйте и сделайте репост.   -  person whatnick    schedule 26.09.2009
comment
@whatnick, так лучше?   -  person Nope    schedule 26.09.2009
comment
форматирование в порядке, но вы имеете в виду левый столбец и правый столбец, а не левую строку и правую строку.   -  person John Machin    schedule 26.09.2009
comment
И ваш верхний левый угол - это x == 0 и y == 0 ... это было бы нижним левым в соглашении большинства людей. Даже антиподы так не делают :-)   -  person John Machin    schedule 26.09.2009
comment
@John Machin: координаты верхнего левого пикселя на вашем экране x=0, y=0. Внизу слева x=0, y=screen_height.   -  person jfs    schedule 26.09.2009
comment
у = высота_экрана?? Это будет либо проигнорировано, либо обработано по модулю screen_height, т.е. ноль :-) Возможно, вы имели в виду (screen_height - 1). В ОП не упоминались ни экран, ни пиксели. Обычное соглашение с графиками на бумаге или на экране состоит в том, что (0, 0) — это нижний левый угол.   -  person John Machin    schedule 26.09.2009
comment
Можно ли эту симуляцию векторизовать, чтобы ее можно было запустить в Numpy, например, могли бы вы одновременно выполнить 1000 таких определений ячеек? Если да, то это можно было бы сделать гораздо быстрее.   -  person tom10    schedule 26.09.2009


Ответы (5)


# initially:
method_list = [
    bottom_left, bottom, bottom_right,
    left, middle, right,
    top_left, top, top_right,
    ]

# each time:
keyx = 0 if not x else (2 if x == self.dim_x - 1 else 1)
keyy = 0 if not y else (2 if y == self.dim_y - 1 else 1)
key = keyy * 3 + keyx
method_list[key](self, x, y, other_args)

Непроверено... но общая идея должна просвечиваться.

Обновление после того, как целевые сообщения были радикально перемещены: «Что-то эффективное было бы хорошо, поскольку эта часть логики вызывается пару миллионов раз… это для имитации отжига»:

Изначально вам не нравилась цепочка тестов, и вы сказали, что вызываете функцию для обработки каждого из 8 случаев. Если вы хотите быстро (в Python): сохраните цепочку тестов и выполняйте обработку каждого случая встроенно вместо вызова функции.

Можно ли использовать психо? Также рассмотрите возможность использования Cython.

person John Machin    schedule 26.09.2009

Если я правильно понимаю, у вас есть набор координат (x, y), живущих в сетке, и вы хотели бы знать, учитывая любую координату, находится ли она внутри сетки или на краю.

Подход, который я бы выбрал, заключается в том, чтобы нормализовать сетку перед сравнением, чтобы ее начало было (0,0), а ее верхний правый угол был (1,1), тогда мне нужно было бы только знать значение координаты для определить его местонахождение. Позволь мне объяснить.

0) Пусть _max представляет собой максимальное значение, а _min, например, x_min является минимальным значением координаты x; пусть _new представляет нормализованное значение.

1) Given (x,y), compute: x_new = (x_max-x)/(x_max-x_min) and y_new=(y_max-y)/(y_max-y_min).

2) [this is pseudo code]
switch y_new:
  case y_new==0: pos_y='bottom'
  case y_new==1: pos_y='top'
  otherwise: pos_y='%2.2f \% on y', 100*y_new
switch x_new:
  case x_new==0: pos_x='left'
  case x_new==1: pos_x='right'
  otherwise: pos_x='%2.2f \% on x', 100*x_new

print pos_y, pos_x

It would print stuff like "bottom left" or "top right" or "32.58% on y 15.43% on x"

Hope that helps.
person Escualo    schedule 26.09.2009

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

Как только появится обычная обработка, скажем, углов, можно было бы предпочесть ловить эти случаи с помощью одного умного оператора if.

person Johannes Charra    schedule 26.09.2009

Что-то вроде этого может быть более читабельным/обслуживаемым. Это, вероятно, будет намного быстрее, чем ваши вложенные операторы if, поскольку оно проверяет каждое условие только один раз и отправляет через словарь, который удобен и быстр.

class LocationThing:

    def __init__(self, x, y):
        self.dim_x = x
        self.dim_y = y

    def interior(self):
        print "interior"
    def left(self):
        print "left"
    def right(self):
        print "right"
    def top(self):
        print "top"
    def bottom(self):
        print "bottom"
    def top_left(self):
        print "top_left"
    def top_right(self):
        print "top_right"
    def bottom_left(self):
        print "bottom_left"
    def bottom_right(self):
        print "bottom_right"

    location_map = {
        # (left, right,   top, bottom)
        ( False, False, False, False ) : interior,
        (  True, False, False, False ) : left,
        ( False,  True, False, False ) : right,
        ( False, False,  True, False ) : top,
        ( False, False, False,  True ) : bottom,
        (  True, False,  True, False ) : top_left,
        ( False,  True,  True, False ) : top_right,
        (  True, False, False,  True ) : bottom_left,
        ( False,  True, False,  True ) : bottom_right,
        }


    def location(self, x,y):
        method = self.location_map[(x==0, x==self.dim_x-1, y==0, y==self.dim_y-1)]
        return method(self)

l = LocationThing(10,10)
l.location(0,0)
l.location(0,1)
l.location(1,1)
l.location(9,9)
l.location(9,1)
l.location(1,9)
l.location(0,9)
l.location(9,0)

Когда вы запускаете выше, он печатает

top_left
left
interior
bottom_right
right
bottom
bottom_left
top_right
person Nick Craig-Wood    schedule 26.09.2009

Для быстрой функции внутреннего цикла вы можете просто стиснуть зубы и сделать уродливое: вложить операторы if else с повторяющимися условиями, чтобы каждое сравнение выполнялось только один раз и выполнялось примерно в два раза быстрее. в качестве примера более чистый ответ (по мобруле):

import timeit

def f0(x, y, x_dim, y_dim):
    if x!=0:
        if x!=x_dim: # in the x interior
            if y!=0:
                if y!=y_dim: # y interior
                    return "interior"
                else: # y==y_dim edge 'top'
                    return "interior-top"
            else:
                return "interior-bottom"
        else: # x = x_dim, "right"
            if y!=0:
                if y!=y_dim: # 
                    return "right-interior"
                else: # y==y_dim edge 'top'
                    return "right-top"
            else:
                return "right-bottom"
    else: # x=0 'left'
        if y!=0:
            if y!=y_dim: # y interior
                return "left-interior"
            else: # y==y_dim edge 'top'
                return "left-top"
        else:
            return "left-bottom"

r_list = ["interior","top","bottom","left","top-left",
            "bottom-left","right","top-right","bottom-right"]                 
def f1(x,y,dim_x,dim_y):
    index = 1*(y==0) + 2*(y==dim_y-1) + 3*(x==0) + 6*(x==dim_x-1)
    return r_list[index]

for x, y, x_dim, y_dim in [(4, 4, 5, 6), (0, 0, 5, 6)]:
    t = timeit.Timer("f0(x, y, x_dim, y_dim)", "from __main__ import f0, f1, x, y, x_dim, y_dim, r_list")
    print "f0", t.timeit(number=1000000)
    t = timeit.Timer("f1(x, y, x_dim, y_dim)", "from __main__ import f0, f1, x, y, x_dim, y_dim, r_list")
    print "f1", t.timeit(number=1000000)

Который дает:

f0 0.729887008667  # nested if-else for interior point (no "else"s)
f1 1.4765329361
f0 0.622623920441  # nested if-else for left-bottom (all "else"s)
f1 1.49259114265

Так что это немного лучше, чем в два раза быстрее, чем ответ mobrule, который был самым быстрым кодом, который, как я знал, будет работать, когда я опубликовал это. (Кроме того, я убрал список строк mobrule из функции, так как это ускорило результат на 50%.) Скорость превыше красоты?

Если вместо этого вам нужно краткое и легко читаемое решение, я предлагаю:

def f1(x, y, x_dim, y_dim):
    d_x = {0:"left", x_dim:"right"}
    d_y = {0:"bottom", y_dim:"top"}
    return d_x.get(x, "interior")+"-"+d_y.get(y, "interior")

который так же быстр, как и другие по моему времени.

person tom10    schedule 26.09.2009
comment
(1) Вы не используете max/dim постоянно. Эти две функции не дают одинаковых результатов, даже после корректировки разницы между верхним и нижним значениями. (2) самый быстрый /выглядящий/ код не обязательно является самым быстрым /работающим/ кодом. Попробуйте вычислить индекс по чему-то вроде (1 if not y else (2 if y == max_y else 0)) + (3 if not x else (6 if x == max_x else 0)) ... это намного более конкурентоспособно (3) используйте python -mtimeit -s"initialisation code" "loop code" вместо самодельных вариантов - person John Machin; 27.09.2009
comment
3) Я думаю, что использую timeit приемлемым способом - если нет, пожалуйста, опубликуйте ссылку, где говорится, что это неправильный способ, поскольку это то, что находится в документации Python. Я не катаю свой собственный, просто запускаю timeit независимо с несколькими разными точками. 2) Если вы думаете, что ваш быстрее, хорошо, проведите тест и докажите это; Я рад, если есть лучший подход, чем мой уродливый ответ. 1) если я сделал опечатку, хорошо, дело в том, что эта структура явно работает и работает быстро. - person tom10; 27.09.2009
comment
(3) см. документы Python - интерфейс командной строки запускает тест 3 раза (по умолчанию) и сообщает о лучшем из 3. (2) Я не утверждаю, что вычисление индекса, о котором я упоминал выше, быстрее, чем тесты if способ; это почти так же хорошо. Я хотел сказать, что это намного быстрее, чем вычисление индекса mobrule. Что лучше, зависит от того, чего на самом деле хочет ОП, скорости или красоты. (1) Это ошибка, а не опечатка. Сначала сделайте это правильно, затем быстро. (4) ваше усилие dict.get(): OP хочет выполнить некоторый неуказанный код для каждого из 8 случаев; этот код вряд ли создаст строковые метки. - person John Machin; 27.09.2009
comment
Я запускаю timeit в соответствии с документацией Python. Что касается моих кодов и моих возможных ошибок, я оставлю их в покое, так как я думаю, что это отвечает на вопрос любого, кто искренне хочет получить ответ. Я рад исправить ошибки, на которые мне указали, но есть ошибка, и я не собираюсь рассказывать вам, где тот, кто публикует непроверенный и несвоевременный код, не стоит моего времени или кого-либо еще в этом отношении. - person tom10; 27.09.2009