получить наименьшую из координат, которые отличаются на N или более в Python - PullRequest
4 голосов
/ 02 июня 2010

предположим, у меня есть список координат:

data = [
    [(10, 20), (100, 120), (0, 5), (50, 60)],
    [(13, 20), (300, 400), (100, 120), (51, 62)]
]

и я хочу взять все кортежи, которые либо появляются в каждом списке в данных, либо любой кортеж, который отличается от , все кортежи в списках, кроме его собственного , на 3 или меньше. Как я могу сделать это эффективно в Python?

Для приведенного выше примера, результаты должны быть:

[[(100, 120), # since it occurs in both lists
  (10, 20), (13, 20), # since they differ by only 3 
  (50, 60), (51, 60)]]

(0, 5) и (300, 400) не будут включены, поскольку они не отображаются в обоих списках и не отличаются от элементов в списках, отличных от их собственного, на 3 или менее.

как это можно вычислить? Благодарю.

Ответы [ 3 ]

1 голос
/ 02 июня 2010

Наивная реализация этого будет медленной: O (n ^ 2), тестирование для каждого узла против каждого другого узла. Используйте дерево, чтобы ускорить его.

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

Оптимизация здесь проста: если мы ищем элементы на евклидовом расстоянии в 3 единицы некоторой точки, и мы знаем, что все элементы в поддереве находятся как минимум в 3 единицах вправо, нет никаких точек в этом районе может быть менее 3 единиц.

Этот код является общественным достоянием. Старайтесь не превращать это в домашнее задание.

#!/usr/bin/python
import math

def euclidean_distance(pos1, pos2):
    x = math.pow(pos1[0] - pos2[0], 2)
    y = math.pow(pos1[1] - pos2[1], 2)
    return math.sqrt(x + y)

class QuadTreeNode(object):
    def __init__(self, pos):
        """
        Create a QuadTreeNode at the specified position.  pos must be an (x, y) tuple.
        Children are classified by quadrant. 
        """
        # Children of this node are ordered TL, TR, BL, BL (origin top-left).
        self.children = [None, None, None, None]
        self.pos = pos

    def classify_node(self, pos):
        """
        Return which entry in children can contain pos.  If pos is equal to this
        node, return None.

        >>> node = QuadTreeNode((10, 20))
        >>> node.classify_node((10, 20)) == None
        True
        >>> node.classify_node((2, 2))
        0
        >>> node.classify_node((50, 2))
        1
        >>> node.classify_node((2, 50))
        2
        >>> node.classify_node((50, 50))
        3

        X boundary condition:
        >>> node.classify_node((10, 2))
        0
        >>> node.classify_node((10, 50))
        2

        Y boundary conditoin:
        >>> node.classify_node((2, 20))
        0
        >>> node.classify_node((50, 20))
        1
        """
        if pos == self.pos:
            return None
        if pos[0] <= self.pos[0]:       # Left
            if pos[1] <= self.pos[1]:   # Top-left
                return 0
            else:                       # Bottom-left
                return 2
        else:                           # Right
            if pos[1] <= self.pos[1]:   # Top-right
                return 1
            else:                       # Bottom-right
                return 3
        assert False, "not reached"

    def add_node(self, node):
        """
        Add a specified point under this node.
        """
        type = self.classify_node(node.pos)
        if type is None:
            # node is equal to self, so this is a duplicate node.  Ignore it.
            return

        if self.children[type] is None:
            self.children[type] = node
        else:
            # We already have a node there; recurse and add it to the child.
            self.children[type].add_node(node)

    @staticmethod
    def CreateQuadTree(data):
        """
        Create a quad tree from the specified list of points.
        """
        root = QuadTreeNode(data[0])
        for val in data[1:]:
            node = QuadTreeNode(val)
            root.add_node(node)

        return root

    def distance_from_pos(self, pos):
        return euclidean_distance(self.pos, pos)

    def __str__(self): return str(self.pos)

    def find_point_within_range(self, pos, distance):
        """
        If a point exists within the specified Euclidean distance of the specified
        point, return it.  Otherwise, return None.
        """
        if self.distance_from_pos(pos) <= distance:
            return self

        for axis in range(0, 4):
            if self.children[axis] is None:
                # We don't have a node on this axis.
                continue

            # If moving forward on this axis would permanently put us out of range of
            # the point, short circuit the search on that axis.
            if axis in (0, 2): # axis moves left on X
                if self.pos[0] < pos[0] - distance:
                    continue
            if axis in (1, 3): # axis moves right on X
                if self.pos[0] > pos[0] + distance:
                    continue
            if axis in (0, 1): # axis moves up on Y
                if self.pos[1] < pos[1] - distance:
                    continue
            if axis in (2, 3): # axis moves down on Y
                if self.pos[1] > pos[1] + distance:
                    continue
            node = self.children[axis].find_point_within_range(pos, distance)
            if node is not None:
                return node
        return None

    @staticmethod
    def find_point_in_range_for_all_trees(point, trees, distance):
        """
        If all QuadTreeNodes in trees contain a a point within the specified distance
        of point, return True,  Otherwise, return False.
        """
        for tree in trees:
            if tree.find_point_within_range(point, distance) is None:
                return False
        return True

def test_naive(data, distance):
    def find_point_in_list(iter, point):
        for i in iter:
            if euclidean_distance(i, point) <= distance:
                return True
        return False

    def find_point_in_all_lists(point):
        for d in data:
            if not find_point_in_list(d, point):
                return False
        return True

    results = []
    for d in data:
        for point in d:
            if find_point_in_all_lists(point):
                results.append(point)
    return set(results)

def test_tree(data, distance):
    trees = [QuadTreeNode.CreateQuadTree(d) for d in data]
    results = []
    for d in data:
        for point in d:
            if QuadTreeNode.find_point_in_range_for_all_trees(point, trees, 3):
                results.append(point)
    return set(results)

def test():
    sample_data = [
            [(10, 20), (100, 120), (0, 5), (50, 60)],
            [(13, 20), (300, 400), (100, 120), (51, 62)]
    ]
    result1 = test_naive(sample_data, 3)
    result2 = test_tree(sample_data, 3)
    print result1
    assert result1 == result2

    # Loosely validate the tree algorithm against a lot of sample data, and compare
    # performance while we're at it:
    def random_data():
        import random
        return [(random.randint(0,1000), random.randint(0,1000)) for d in range(0,500)]
    data = [random_data() for x in range(0,10)]

    print "Searching (naive)..."
    result1 = test_naive(data, 3)

    print "Searching (tree)..."
    result2 = test_tree(data, 3)
    assert result1 == result2


if __name__ == "__main__":
    test()

    import doctest
    doctest.testmod()
0 голосов
/ 02 июня 2010
Интуиция

@ barrycarter интересна: чтобы уменьшить количество сравнений (где под «сравнением» двух точек мы подразумеваем проверку, равно ли их расстояние <= 3), «фактически нарезать» 2D-плоскость на подходящие «ячейки», чтобы каждую точку нужно сравнивать только с точками в соседних «ячейках». Это действительно может помочь (в случае грубого решения, где каждая точка должна сравниваться с практически всеми остальными), если ваш набор данных большой.

Вот реализация идеи на Python (поскольку эскиз кода Барри выглядит как Perl или что-то в этом роде), направленная на ясность, а не на скорость ...:

import collections
import math


def cellof(point):
    x, y = point
    return x//3, y//3

def distance(p1, p2):
    return math.hypot(p1[0]-p2[0], p1[1]-p2[1])

def process(data):
    cells = collections.defaultdict(list)
    for i, points in enumerate(data):
        for p in points:
            cx, cy = cellof(p)
            cells[cx, cy].append((i, p))
    res = set()
    for c, alist in cells.items():
        for i, p in alist:
                for cx in range(c[0]-1, c[0]+2):
                    for cy in range(c[1]-1, c[1]+2):
                        otherc = cells[cx, cy]
                        for otheri, otherp in otherc:
                            if i == otheri: continue
                            dst = distance(p, otherp)
                            if dst <= 3: res.add(p)
    return sorted(res)

if __name__ == '__main__':  # just an example

    data = [
        [(10, 20), (100, 120), (0, 5), (50, 60)],
        [(13, 20), (300, 400), (100, 120), (51, 62)]
    ]
    print process(data)

При запуске в виде скрипта выводится

[(10, 20), (13, 20), (50, 60), (51, 62), (100, 120)]

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

0 голосов
/ 02 июня 2010

Я надеюсь, это поможет вам начать. Любые улучшения будут с благодарностью.

Появляется во всех списках тривиально - достаточно просто пересечь все элементы в списке.

>>> data = [
...     [(10, 20), (100, 120), (0, 5), (50, 60)],
...     [(13, 20), (300, 400), (100, 120), (51, 62)]
... ]
>>> dataset = [set(d) for d in data]
>>> dataset[0].intersection(*dataset[1:])
set([(100, 120)])

«Различается на 3 или меньше» с кортежами, отличными от тех, которые находятся в том же списке, мне кажется проблемой с графиком / 2D пространством. Для этого не существует простого алгоритма, без полиномиального алгоритма, и если ваш набор данных не очень большой, вы можете просто перебрать их и сгруппировать точки закрытия, которые не находятся в одном списке.

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