web-dev-qa-db-ja.com

N個の長方形の重なりを見つける効率的な方法

長方形が2つの別々のリストに格納されているn個の長方形のオーバーラップを見つけるための効率的な解決策を見つけようとしています。 listAの長方形と重なるlistBのすべての長方形を探しています(またはその逆)。 1つの要素を最初のリストから2番目のリストに比較すると、非常に長い時間がかかる可能性があります。私は効率的な解決策を探しています。

長方形のリストが2つあります

rect = Rectangle(10, 12, 56, 15)
rect2 = Rectangle(0, 0,1, 15)
rect3 = Rectangle (10,  12, 56, 15)

listA = [rect, rect2]
listB = [rect3]

これはクラスから作成されます:

import numpy as np
import itertools as it

class  Rectangle(object):
    def __init__(self, left, right, bottom, top):
        self.left = left
        self.bottom = right
        self.right = bottom
        self.top = top

    def overlap(r1, r2):
        hoverlaps = True
        voverlaps = True
        if (r1.left > r2.right) or (r1.right < r2.left):
            hoverlaps = False
        if (r1.top < r2.bottom) or (r1.bottom > r2.top):
            voverlaps = False
        return hoverlaps and voverlaps

listAの長方形をlistBと比較する必要があります。コードは次のようになり、非常に非効率的です。1つずつ比較します。

for a in it.combinations(listB):
    for b in it.combinations(listA):
        if a.overlap(b):

問題に対処するためのより効率的な方法はありますか?

11
karu

最初に: 計算幾何学 からの多くの問題と同様に、成長順序分析のパラメーターを指定するには注意が必要です:リストの長さを呼び出すmおよびn、最悪の場合これらのパラメーターだけでΩ(m×n)、すべての領域が重複する可能性があるため(この点では、質問のアルゴリズムは 漸近的に最適 )です。通常、出力のサイズを含めます:t = f(m、n、o)出力に依存するアルゴリズム )。
自明なことですが、f∈Ω(m + n + o)提示された問題に対して。


Line Sweep は、幾何学的問題を1次元削減するためのパラダイムです。元の形式では、2Dから1D、平面から線になります。

平面内のすべての長方形、リストのさまざまな色を想像してみてください。
ここで、この平面を横切る線をスイープします-通常は左から右へ、そして無限小「低いy座標の場合」(増加する座標を処理するx-順序、増加y-等しい順序x)。
このすべてについてsweep(またはscan)、色ごとに、現在のx座標でのすべての長方形の「y間隔」を表す1つのセットを保持します、空から始めます。 (挿入、削除、および重複するすべての間隔の列挙をサポートするデータ構造内クエリ間隔:以下を参照してください。)
長方形の左側に合わせて、その色のセグメントをデータ構造に追加します。他の色で重なり合う間隔/長方形を報告します。
右側で、セグメントを削除します。
「オーバーラップ」の定義に応じて、右側の前に左側を処理します。またはその逆を行います。


間隔の挿入と削除をサポートし、クエリ間隔と重複するすべての間隔を見つけるデータ構造は多数あります。現在、私は Augmented Search-Trees が理解、実装、テスト、分析するのが最も簡単かもしれないと思います…
これを使用して、すべてのo交差する軸に沿った長方形のペアを列挙する(a、b)listAおよびlistBからO ((m + n)log(m + n)+ o)時間とO(m + n)スペース。かなりの問題のインスタンスの場合、線形空間以上を必要とするデータ構造を避けます((元の) セグメントツリー 、間隔の重複に関する1つの例)。


アルゴリズム設計のもう1つのパラダイムは、 Divide&Conquer :計算幾何学の問題で、問題を独立した部分に分割できる1つの次元と、「下の座標」のサブ問題と「上記の座標」は、予想される実行時間に近いです。おそらく、「座標を含む」別の(そして異なる)サブ問題を解決する必要があります。これは、a)サブ問題を解決するための実行時間が「超対数線形」であり、b)サブ問題のソリューションから全体的なソリューションを構築する安価な(線形)方法がある場合に有益になる傾向があります。 。
これは同時問題解決に役立ち、ラインスイープを含むサブ問題の他のアプローチと一緒に使用できます。


各アプローチを微調整する方法はたくさんあります。まず、出力に貢献できない可能性のある入力項目を無視することから始めます。同様の成長順序のアルゴリズムの実装を「公正に」比較するには、公正な「微調整のレベル」を目指しないでください。微調整にかなりの時間を費やすようにしてください。

14
greybeard

いくつかの潜在的なマイナーな効率の改善。まず、overlap()関数を修正します。これにより、必要のない計算が行われる可能性があります。

def overlap(r1, r2):

    if r1.left > r2.right or r1.right < r2.left:
        return False

    if r1.top < r2.bottom or r1.bottom > r2.top:
        return False

    return True

次に、リストの1つに含まれる長方形を計算し、それを使用して他のリストをスクリーニングします。コンテナーと重ならない長方形は、テストする必要がありませんall寄与した長方形それに:

def containing_rectangle(rectangles):
    return Rectangle(min(rectangles, key=lambda r: r.left).left,
        max(rectangles, key=lambda r: r.right).right,
        min(rectangles, key=lambda r: r.bottom).bottom,
        max(rectangles, key=lambda r: r.top).top
    )

c = containing_rectangle(listA)

for b in listB:
    if b.overlap(c):
        for a in listA:
            if b.overlap(a):

何百ものランダムな長方形を使用したテストでは、これにより1桁のパーセンテージ(2%や3%など)のオーダーでの比較が回避され、比較の数が増えることがありました。ただし、おそらくデータはランダムではなく、このタイプのスクリーニングの方がうまくいく可能性があります。

データの性質に応じて、これをコンテナの長方形に分割して、50Kから10Kの長方形のバッチごとにチェックするか、スライスによって最大の効率が得られます。おそらく、長方形をコンテナバッチに割り当てる前に、長方形を(中心などで)事前に並べ替えます。

コンテナの長方形を使用して、両方のリストを分割してバッチ処理できます。

listAA = [listA[x:x + 10] for x in range(0, len(listA), 10)]

for i, arrays in enumerate(listAA):
    listAA[i] = [containing_rectangle(arrays)] + arrays

listBB = [listB[x:x + 10] for x in range(0, len(listB), 10)]

for i, arrays in enumerate(listBB):
    listBB[i] = [containing_rectangle(arrays)] + arrays

for bb in listBB:
    for aa in listAA:
        if bb[0].overlap(aa[0]):
            for b in bb[1:]:
                if b.overlap(aa[0]):
                    for a in aa[1:]:
                        if b.overlap(a):

私のランダムデータでは、これにより、コンテナの長方形の比較を数えても、比較が15%から20%のオーダーで減少しました。上記の長方形のバッチ処理は任意であり、おそらくもっとうまくいくことができます。

5
cdlane

発生する例外は、表示するコードの最後の行からのものです。 listはクラスであり、そのコンテキストのlist[rect]構文がインデックスを作成しようとしているため、式[]は無効です。おそらく[rect](単一のアイテムrectを含む新しいリストを作成する)だけが必要です。

コードには、他にもいくつかの基本的な問題があります。たとえば、Rect.__init__メソッドはleft属性を設定しません。これは、衝突テストメソッドで期待されているようです。また、overlapメソッドのさまざまな部分でr1r2に異なる大文字を使用しました(Pythonはr1R1と同じとは見なしません)。

これらの問題は、あなたの質問が尋ねる3つ以上の長方形のテストとは実際には何の関係もありません。これを行う最も簡単な方法は(上記のような基本的な問題がある場合は、単純なアルゴリズムに固執することを強くお勧めします)、既存のペアワイズテストを使用して各長方形を互いに比較することです。 itertools.combinationsを使用して、反復可能オブジェクト(リストなど)からアイテムのすべてのペアを簡単に取得できます。

list_of_rects = [rect1, rect2, rect3, rect4] # assume these are defined elsewhere

for a, b in itertools.combinations(list_of_rects, 2):
    if a.overlap(b):
        # do whatever you want to do when two rectangles overlap here
4
Blckknght

明らかに、リスト(少なくともlistB)がr2.xminでソートされている場合は、listBでr1.xmaxを検索し、このlistBでr1の重なりのテストを停止できます(残りは右側にあります)。これはO(n・log(n))になります。

ソートされたベクトルは、ソートされたリストよりも高速にアクセスできます。

長方形のエッジが軸と同じ方向を向いていると思います。

また、cdlaneが説明したように、overlap()関数を修正します。

3
Ripi2

私が行ったテストによると、numpyを使用したこの実装は約35〜40倍高速です。それぞれ10000個のランダムな長方形を持つ2つのリストの場合、この方法は2.5秒かかり、問題の方法は約90秒かかりました。複雑さという点では、問題のメソッドのようにO(N ^ 2)のままです。

import numpy as np

rects1=[
    [0,10,0,10],
    [0,100,0,100],
]

rects2=[
    [20,50,20,50],
    [200,500,200,500],
    [0,12,0,12]
]

data=np.asarray(rects2)


def find_overlaps(rect,data):
    data=data[data[::,0]<rect[1]]
    data=data[data[::,1]>rect[0]]
    data=data[data[::,2]<rect[3]]
    data=data[data[::,3]>rect[2]]
    return data


for rect in rects1:
    overlaps = find_overlaps(rect,data)
    for overlap in overlaps:
        pass#do something here
2
user4421975

座標の上限と下限がわかっている場合は、座標空間を正方形に分割することで検索を絞り込むことができます。 100x100。

  • 座標の正方形ごとに1つの「セット」を作成します。
  • すべての正方形を通過し、重なり合う正方形の「セット」に配置します。

参照 タイルレンダリング パーティションを使用してグラフィカル操作を高速化します。

    // Stores rectangles which overlap (x, y)..(x+w-1, y+h-1)
    public class RectangleSet
    {
       private List<Rectangle> _overlaps;

       public RectangleSet(int x, int y, int w, int h);
    }

    // Partitions the coordinate space into squares
    public class CoordinateArea
    {
       private const int SquareSize = 100;

       public List<RectangleSet> Squares = new List<RectangleSet>();

       public CoordinateArea(int xmin, int ymin, int xmax, int ymax)
       {
          for (int x = xmin; x <= xmax; x += SquareSize)
          for (int y = ymin; y <= ymax; y += SquareSize)
          {
              Squares.Add(new RectangleSet(x, y, SquareSize, SquareSize);
          }
       }

       // Adds a list of rectangles to the coordinate space
       public void AddRectangles(IEnumerable<Rectangle> list)
       {
          foreach (Rectangle r in list)
          {
              foreach (RectangleSet set in Squares)
              {
                  if (r.Overlaps(set))
                      set.Add(r);
              }
          }
       }
    }

これで、比較用の長方形のセットがはるかに小さくなり、処理速度が大幅に向上するはずです。

CoordinateArea A = new CoordinateArea(-500, -500, +1000, +1000);
CoordinateArea B = new CoordinateArea(-500, -500, +1000, +1000);  // same limits for A, B

A.AddRectangles(listA);
B.AddRectangles(listB);

for (int i = 0; i < listA.Squares.Count; i++)
{
    RectangleSet setA = A[i];
    RectangleSet setB = B[i];

    // *** small number of rectangles, which you can now check thoroghly for overlaps ***

}
2
user1023602

二次から線形への時間計算量を減らすために、重複する可能性のある近くの長方形にすばやくアクセスするには、追加のデータ構造(空間インデックス)を設定する必要があると思います。

参照:

1
otmar

これは、多くの候補長方形(candidate_coords [[l、t、r、b]、...])とターゲット長方形(target_coords [l、t、r、b])のオーバーラップ領域を計算するために使用するものです。

comb_tensor = np.zeros((2, candidate_coords.shape[0], 4))

comb_tensor[0, :] = target_coords
comb_tensor[1] = candidate_coords

dx = np.amin(comb_tensor[:, :, 2].T, axis=1) - np.amax(comb_tensor[:, :, 0].T, axis=1)
dy = np.amin(comb_tensor[:, :, 3].T, axis=1) - np.amax(comb_tensor[:, :, 1].T, axis=1)

dx[dx < 0] = 0
dy[dy < 0] = 0 

overlap_areas = dx * dy

これは、すべてがndarrayで動作するnumpy関数を使用して行われるため、候補の長方形が多数ある場合は特に効率的です。重なり領域を計算するループを実行するか、comb_tensorにもう1つの次元を追加することができます。

0
M.Racko