AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Wie kann man viele Punkte schnell vergleichen?

Ein Thema von Matze · begonnen am 15. Jun 2010 · letzter Beitrag vom 18. Jun 2010
Antwort Antwort
Medium

Registriert seit: 23. Jan 2008
3.688 Beiträge
 
Delphi 2007 Enterprise
 
#1

AW: Wie kann man viele Punkte schnell vergleichen?

  Alt 15. Jun 2010, 22:46
Im 3D-Bereich wird zum "culling" (Ablehnen von Polygonen/Vertices) gerne das Separating Axis Theorem ausgenutzt. Gerade mit achsenparallelen Rechtecken kann man da einiges optimieren, und das kann extrem flott gezüchtet werden. (Wird immerhin zum Frustum-Culling teils Bild für Bild in Spielen gemacht, und sogar in 3D mit schiefen Ebenen.) Es gibt die Wikipediaseite auch auf Deutsch, ich fand die Englische jetzt nur weit aus anschaulicher.
Das dürfte mehr oder minder State of the Art sein, es sei denn es gibt für 2D eine noch flottere Klamotte die mir dann unbekannt wäre
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.731 Beiträge
 
Delphi 12 Athens
 
#2

AW: Wie kann man viele Punkte schnell vergleichen?

  Alt 16. Jun 2010, 00:55
Wenn ich das recht verstehe, betrachtest du lediglich die X-Werte der Punkte mit den jeweiligen Fenstergrenzen. Die Y-Werte interessieren dabei noch gar nicht.

Die Punkte scheinen in X aufsteigend sortiert zu sein, sonst funktioniert dein Algorithmus nämlich nicht.

Wenn man nun die Fenstergrenzen ebenfalls sortiert und mit Index-Arrays arbeitet, kann man mit zwei Durchläufen die Punktbereiche in den Fenstern ermitteln.

Hier der passende Code:

Delphi-Quellcode:
{ Punkte gibt die X-Werte der Punkte an, FensterLo die Untergrenzen, FensterHi die Obergrenzen der Fenster.
  insideLo und insideHi geben die Indizes der Punkte an, die gerade noch in dem jeweiligen Fenster liegen }

procedure FindeFenster(const Punkte, FensterLo, FensterHi: array of Double; var insideLo, insideHi: TIntegerDynArray);
var
  CntPunkte: Integer;
  CntFenster: Integer;
  orderLo: TIntegerDynArray;
  orderHi: TIntegerDynArray;
  I: Integer;
  idx: Integer;
  rng: Integer;
begin
  CntPunkte := Length(Punkte);
  CntFenster := Length(FensterLo);

  Assert(CntPunkte > 0);
  Assert(CntFenster > 0);
  Assert(CntFenster = Length(FensterHi));

  SetLength(insideLo, CntFenster);
  SetLength(insideHi, CntFenster);

  { orderLo enthält eine Liste von Indizes auf FensterLo, so daß die Werte aufsteigend sortiert sind.
    orderHi enthält eine Liste von Indizes auf FensterHi, so daß die Werte aufsteigend sortiert sind.
    Läßt sich mit einem angepassten QuickSort leicht implementieren. }


  orderLo := GetSortOrder(FensterLo);
  orderHi := GetSortOrder(FensterHi);

  { erster Durchlauf: untere Grenzen ermitteln }
  idx := 0;
  rng := orderLo[idx];
  for I := 0 to Length(Punkte) - 1 do begin
    if Punkte[I] >= FensterLo[rng] then begin
      { untere Grenze des nächsten Fensters überschritten }
      insideLo[rng] := I;
      { die folgenden Fenster prüfen bis wir eins finden, in dem wir noch nicht sind }
      Inc(idx);
      while idx < CntFenster do begin
        rng := orderLo[idx];
        if Punkte[I] < FensterLo[rng] then
          Break;
        insideLo[rng] := I;
        Inc(idx);
      end;
      { schon alle Fenster überprüft? }
      if idx >= CntFenster then
        Break;
      rng := orderLo[idx]
    end;
  end;

  { zweiter Durchlauf: obere Grenzen ermitteln }
  idx := CntFenster - 1;
  rng := orderHi[idx];
  for I := CntPunkte - 1 downto 0 do begin
    if Punkte[I] <= FensterHi[rng] then begin
      { obere Grenze des nächsten Fensters unterschritten }
      insideHi[rng] := I;
      { die folgenden Fenster prüfen bis wir eins finden, in dem wir noch nicht sind }
      Dec(idx);
      while idx >= 0 do begin
        rng := orderHi[idx];
        if Punkte[I] > FensterHi[rng] then
          Break;
        insideHi[rng] := I;
        Dec(idx);
      end;
      { schon alle Fenster überprüft? }
      if idx < 0 then
        Break;
      rng := orderHi[idx];
    end;
  end;
end;
Die Funktion GetSortOrder ruft einen angepassten Quicksort auf:

Delphi-Quellcode:
procedure QuickSort(const Values: array of Double; var Index: array of Integer; L, R: Integer);
var
  I, J, T: Integer;
  P: Double;
begin
  repeat
    I := L;
    J := R;
    P := Values[Index[(L + R) shr 1]];
    repeat
      while (Values[Index[I]] < P) do
        Inc(I);
      while (Values[Index[J]] > P) do
        Dec(J);
      if I <= J then
      begin
        if I <> J then
        begin
          T := Index[I];
          Index[I] := Index[J];
          Index[J] := T;
        end;
        Inc(I);
        Dec(J);
      end;
    until I > J;
    if L < J then
      QuickSort(Values, Index, L, J);
    L := I;
  until I >= R;
end;

function GetSortOrder(const Values: array of Double): TIntegerDynArray;
var
  I: Integer;
begin
  SetLength(result, Length(Values));
  for I := 0 to Length(result) - 1 do
    result[I] := I;

  QuickSort(Values, result, 0, Length(result) - 1);
end;
Uwe Raabe
  Mit Zitat antworten Zitat
Medium

Registriert seit: 23. Jan 2008
3.688 Beiträge
 
Delphi 2007 Enterprise
 
#3

AW: Wie kann man viele Punkte schnell vergleichen?

  Alt 16. Jun 2010, 01:18
Öhm, sortiert und nur für eine Achse ginge das doch noch VIEL einfacher! Die Fenstergrenzen müssen dafür auch nach gleicher Weise wie die Punkte sortiert sein, und du kommst mit nur einem Durchlauf aus.

Mal so ein Fetzen Pseudocode:
Delphi-Quellcode:
type
  TWindow = class;
  TEdge = class
  public
    Value: Float;
    IsLeftEdge: Boolean; // im Konstruktor zuweisen, wird noch wichtig :)
    Window: TWindow; // im Konstruktor zuweisen, quasi der Parent, also Rückbezug
  end;
  TWindow = class
  public
    Left, Right: TEdge;
    LeftPointLeft: Float;
    LeftPointRight: Float;
    RightPointLeft: Float;
    RightPointRight: Float;
  end;

var
  Windows: array of TWindow;
  Edges: array of TEdge; // referenzen auf alle Edges aus dem Windows-Array sortiert nach Value
  CurrentEdgeIndex: Integer;
begin
  for i := 0 to maxPoints do
  begin
    if Points[i].X > Edges[CurrentEdgeIndex].Value then
    begin
      if Edges[CurrentEdgeIndex].IsLeftEdge then
      begin
        Edges[CurrentEdgeIndex].Window.LeftPointLeft := Points[i-1].Value;
        Edges[CurrentEdgeIndex].Window.LeftPointRight := Points[i].Value;
      end
      else
      begin
        Edges[CurrentEdgeIndex].Window.RightPointLeft := Points[i-1].Value;
        Edges[CurrentEdgeIndex].Window.RightPointRight := Points[i].Value;
      end;
      inc(CurrentEdgeIndex);
    end;
  end;
end;
Und schwupps stehen im TWindows-Array alle Punkte, die links und rechts der X-Fenstergrenzen liegen, in O(n).

Man könnte natürlich auch die Punkte in die TEdge Instanzen werfen, und sich dieses IsLeftEdge damit sparen:
Delphi-Quellcode:
type
  TWindow = class;
  TEdge = class
  public
    Value: Float;
    LeftValue, RightValue: Float;
  end;
  TWindow = class
  public
    Left, Right: TEdge;
  end;

var
  Windows: array of TWindow;
  Edges: array of TEdge; // referenzen auf alle Edges aus dem Windows-Array sortiert nach Value
  CurrentEdgeIndex: Integer;
begin
  for i := 0 to maxPoints do
  begin
    if Points[i].X > Edges[CurrentEdgeIndex].Value then
    begin
      Edges[CurrentEdgeIndex].LeftValue := Points[i-1].Value;
      Edges[CurrentEdgeIndex].RightValue := Points[i].Value;
      inc(CurrentEdgeIndex);
    end;
  end;
end;
Das spart evtl. noch (sehr minimal) Zeit in der Schleife (ein Vergleich und ein paar Dereferenzierungen, uhu), und die Werte stehen ggf. an sinnvollerer Stelle, je nach dem wie du es nachher brauchst.
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)

Geändert von Medium (16. Jun 2010 um 01:23 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.731 Beiträge
 
Delphi 12 Athens
 
#4

AW: Wie kann man viele Punkte schnell vergleichen?

  Alt 16. Jun 2010, 08:00
@Medium: Es gibt da neben ein paar Grenzfällen, in denen der Code einen ERangeError auslöst (z.B. erster Punkt liegt innerhalb des ersten Fensters, letzter Punkt liegt außerhalb des letzten Fensters), noch das Problem, daß er fehlerhafte Resultate liefert. Mit den gegebenen Werten ergänzt um ein paar weitere Fenster mit Spezialfällen (drittes Fenster umschließt erstes, kompletter Bereich, genau ein Punkt, nur ein Punkt im Bereich) bekomme ich folgende Ergebnisse:

Code:
Punkte: 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 

erwartet: -------------------------------------------------------------
0.7-1.8: 1.0, 1.5
1.4-3.7: 1.5, 2.0, 2.5, 3.0, 3.5
0.6-2.1: 1.0, 1.5, 2.0
0.0-5.0: 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0
2.0-2.0: 2.0
2.9-3.1: 3.0

erhalten: -------------------------------------------------------------
0.7-1.8: 1.5 , 2.0 
1.4-3.7:
0.6-2.1: 1.0 , 1.5 , 2.0 , 2.5 , 3.0 , 3.5 
0.0-5.0:
2.0-2.0: 3.0 
2.9-3.1: 4.5
Uwe Raabe
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu
Online

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.505 Beiträge
 
Delphi 12 Athens
 
#5

AW: Wie kann man viele Punkte schnell vergleichen?

  Alt 16. Jun 2010, 08:18
Hab jetzt nur mal auf die Schnelle das Find der TStringList versucht anzupassen und hoffe es stimmt soweit auch.
Delphi-Quellcode:
type TMyArray = array of record X, Y: Integer; end;

function FindLowerXIndex(const Arr: TMyArray; LowX: Integer): Integer;
var
  L, H, I: Integer;
begin
  L := 0;
  H := High(Arr);
  while L <= H do
  begin
    I := (L + H) shr 1;
    if Arr[I].X < LowX then L := I + 1 else H := I - 1;
  end;
  Result := L;
end;
Das Array muß natürlich nach X sortiert sein.
Ein Therapeut entspricht 1024 Gigapeut.
  Mit Zitat antworten Zitat
Medium

Registriert seit: 23. Jan 2008
3.688 Beiträge
 
Delphi 2007 Enterprise
 
#6

AW: Wie kann man viele Punkte schnell vergleichen?

  Alt 16. Jun 2010, 09:58
@Medium: Es gibt da neben ein paar Grenzfällen, in denen der Code einen ERangeError auslöst (z.B. erster Punkt liegt innerhalb des ersten Fensters, letzter Punkt liegt außerhalb des letzten Fensters)
Dessen war ich mir bewusst, war aber gestern Nacht zu faul explizit drauf einzugehen - ich nahm einfach mal an, dass das offensichtlich sein, dass wenn solche Fälle existieren, sie einer Spezialbehandlung bedürfen

Zitat:
noch das Problem, daß er fehlerhafte Resultate liefert. Mit den gegebenen Werten ergänzt um ein paar weitere Fenster mit Spezialfällen (drittes Fenster umschließt erstes, kompletter Bereich, genau ein Punkt, nur ein Punkt im Bereich) bekomme ich folgende Ergebnisse:
Wie gesagt, das hab ich durchaus erwartet. Sobald Fenster Edges teilen (bzw. identische haben), ein Fenster 0 Punkte umschließt oder Edges ausserhalb des Bereiches liegen, müsste ein wenig ge-if-then-else betrieben werden. Meine Intention war auch nicht, einen fix und fertigen Algo einzustellen, sondern nur fix die spontane Idee zu illustrieren

Schöne Grüße,
Medium
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 15:49 Uhr.
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz