1

シンプルなライン クラスは、開始座標と終了座標を含む 2 つの PointF メンバーとして定義されます。

public class Line    {
    PointF s, e;
}

描画キャンバスに表示され、1 つまたは複数のテーブルを形成するすべての水平線と垂直線を含む 2 つのリストがあります。

List<Line> AllHorizontalLines;
List<Line> AllVerticalLines;

1 つのテーブルに属する行が 1 つのグループに取り込まれるように、これらの行をグループ化する必要があるため、グループ化関数には次のような署名があります。

List<List<Line>> GroupLines(List<Line> hor, List<Line> ver)
{ 

}

簡単にするために、ページには「単純な」テーブルのみがあると仮定しています。つまり、ネストされたテーブルはありません。ただし、結合されたセルが存在する可能性があるため、親テーブルの高さ全体よりも小さい小さな水平線と垂直線を無視する必要があります。さらに簡単にするために、両方の入力リストがソートされていると仮定します (水平線は Y 軸、垂直線は X 軸)。

この問題を解決する既知のアルゴリズムはありますか? または、誰かが私が考案するのを手伝ってくれますか?

4

3 に答える 3

1

以下はうまくいくようです:

  • 境界の四角形を各四角形の線のリストにマッピングする辞書を設定します。
  • 両方の入力リストの各行に対して (方向は気にしません)
    • 線の外に外接する四角形を作成する
    • 線が既存の境界矩形と交差するかどうかを確認します。
      • その場合、それらの四角形からの線をマージし、現在の行を追加し、接触した四角形を削除し、新しい境界四角形を計算して、もう一度確認します。
      • それ以外の場合は、この新しい四角形を辞書に追加し、古い四角形を削除します。
  • 各長方形から行のリストを返します。

これが私が持っているコードです:

public static IEnumerable<IEnumerable<Line>> GroupLines(IEnumerable<Line> lines)
{
    var grouped = new Dictionary<Rectangle, IEnumerable<Line>>();

    foreach (var line in lines)
    {
        var boundedLines = new List<Line>(new[] { line });
        IEnumerable<Rectangle> crossedRectangles;
        var boundingRectangle = CalculateRectangle(boundedLines);
        while (
            (crossedRectangles = grouped.Keys
                .Where(r => Crosses(boundingRectangle, r))
                .ToList()
            ).Any())
        {
            foreach (var crossedRectangle in crossedRectangles)
            {
                boundedLines.AddRange(grouped[crossedRectangle]);
                grouped.Remove(crossedRectangle);
            }
            boundingRectangle = CalculateRectangle(boundedLines);
        }
        grouped.Add(boundingRectangle, boundedLines);
    }
    return grouped.Values;
}

public static bool Crosses(Rectangle r1, Rectangle r2)
{
    return !(r2.Left > r1.Right ||
        r2.Right < r1.Left ||
        r2.Top > r1.Bottom ||
        r2.Bottom < r1.Top);
}

public static Rectangle CalculateRectangle(IEnumerable<Line> lines)
{
    Rectangle rtn = new Rectangle
    {
        Left = int.MaxValue,
        Right = int.MinValue,
        Top = int.MaxValue,
        Bottom = int.MinValue
    };

    foreach (var line in lines)
    {
        if (line.P1.X < rtn.Left) rtn.Left = line.P1.X;
        if (line.P2.X < rtn.Left) rtn.Left = line.P2.X;
        if (line.P1.X > rtn.Right) rtn.Right = line.P1.X;
        if (line.P2.X > rtn.Right) rtn.Right = line.P2.X;
        if (line.P1.Y < rtn.Top) rtn.Top = line.P1.Y;
        if (line.P2.Y < rtn.Top) rtn.Top = line.P2.Y;
        if (line.P1.Y > rtn.Bottom) rtn.Bottom = line.P1.Y;
        if (line.P2.Y > rtn.Bottom) rtn.Bottom = line.P2.Y;
    }

    return rtn;
}

一緒にノックしたWinFormsアプリ

于 2012-09-14T12:31:39.433 に答える
1

これが私の提案するアプローチです:

  • 完全に含まれる「小さい」行がないように、両方のリストを消去します。
  • 任意の行を選択します。
  • この線に接する (交差する) すべての線を取ります。
  • これらの線のそれぞれについて、それらに接するすべての線を取ります。
  • 触れる線がなくなるまで続けます。
  • これでグループができました。
  • 残っている行から 1 行を選び、行がなくなるまで繰り返します。

コード:

public static IEnumerable<IEnumerable<Line>> Group(IEnumerable<Line> horizontalLines, IEnumerable<Line> verticalLines)
{
  // Clean the input lists here. I'll leave the implementation up to you.

  var ungroupedLines = new HashSet<Line>(horizontalLines.Concat(verticalLines));
  var groupedLines = new List<List<Line>>();

  while (ungroupedLines.Count > 0)
  {
    var group = new List<Line>();
    var unprocessedLines = new HashSet<Line>();
    unprocessedLines.Add(ungroupedLines.TakeFirst());

    while (unprocessedLines.Count > 0)
    {
      var line = unprocessedLines.TakeFirst();
      group.Add(line);
      unprocessedLines.AddRange(ungroupedLines.TakeIntersectingLines(line));
    }

    groupedLines.Add(group);
  }

  return groupedLines;
}

public static class GroupingExtensions
{
  public static T TakeFirst<T>(this HashSet<T> set)
  {
    var item = set.First();
    set.Remove(item);
    return item;
  }

  public static IEnumerable<Line> TakeIntersectingLines(this HashSet<Line> lines, Line line)
  {
    var intersectedLines = lines.Where(l => l.Intersects(line)).ToList();
    lines.RemoveRange(intersectedLines);
    return intersectedLines;
  }

  public static void RemoveRange<T>(this HashSet<T> set, IEnumerable<T> itemsToRemove)
  {
    foreach(var item in itemsToRemove)
    {
      set.Remove(item);
    }
  }

  public static void AddRange<T>(this HashSet<T> set, IEnumerable<T> itemsToAdd)
  {
    foreach(var item in itemsToAdd)
    {
      set.Add(item);
    }
  }
}

このメソッドを Line に追加します

public bool Intersects(Line other)
{
  // Whether this line intersects the other line or not.
  // I'll leave the implementation up to you.
}

ノート:

このコードの実行が遅すぎる場合は、水平方向にスキャンして、接続された行を取得する必要がある場合があります。これも一見の価値があるかもしれません。

専門:

public static IEnumerable<IEnumerable<Line>> Group(IEnumerable<Line> horizontalLines, IEnumerable<Line> verticalLines)
{
  // Clean the input lists here. I'll leave the implementation up to you.

  var ungroupedHorizontalLines = new HashSet<Line>(horizontalLines);
  var ungroupedVerticalLines = new HashSet<Line>(verticalLines);
  var groupedLines = new List<List<Line>>();

  while (ungroupedHorizontalLines.Count + ungroupedVerticalLines.Count > 0)
  {
    var group = new List<Line>();
    var unprocessedHorizontalLines = new HashSet<Line>();
    var unprocessedVerticalLines = new HashSet<Line>();

    if (ungroupedHorizontalLines.Count > 0)
    {
      unprocessedHorizontalLines.Add(ungroupedHorizontalLines.TakeFirst());
    }
    else
    {
      unprocessedVerticalLines.Add(ungroupedVerticalLines.TakeFirst());
    }

    while (unprocessedHorizontalLines.Count + unprocessedVerticalLines.Count > 0)
    {
      while (unprocessedHorizontalLines.Count > 0)
      {
        var line = unprocessedHorizontalLines.TakeFirst();
        group.Add(line);
                unprocessedVerticalLines.AddRange(ungroupedVerticalLines.TakeIntersectingLines(line));
      }
      while (unprocessedVerticalLines.Count > 0)
      {
        var line = unprocessedVerticalLines.TakeFirst();
        group.Add(line);
        unprocessedHorizontalLines.AddRange(ungroupedHorizontalLines.TakeIntersectingLines(line));
      }
    }
    groupedLines.Add(group);
  }

  return groupedLines;
}

これは、水平線が他の水平線に接触しているかどうかをチェックしないため、線が重なっていないことを前提としています (垂直についても同じです)。

おそらく、if-else を削除できます。これは、水平線に接続されていない垂直線がある場合に備えてあります。

于 2012-09-14T12:51:12.373 に答える
0

わかった。私はついに効率的なアルゴリズムを自分で考案しました。将来の読者のための手順は次のとおりです。

  1. のグランド コレクションを定義しTuple<List<Line>, List<Line>>ます。このコレクションの各タプルは、1 つのテーブル (そのテーブルのすべての水平線と垂直線) を表します。

  2. 一方の端で水平線の開始点に接し、もう一方の端で別の水平線の開始点に接するすべての垂直線を見つけます。これらは、各テーブルの一番左の行になります。

  3. 次に、各左線 (LV と呼びます) について: LV と同じ開始 Y と終了 Y を持ち、同じテーブルに属するすべての垂直線を見つけます。これらの「姉妹」行を MyVSisters というリストに追加します。

    b. 右端の垂直姉妹線の X 座標 (RVX と呼びます) を見つけます。

    c. 次に、LV の X から RVX まで伸び、LV の Y1 と Y2 の間に Y があるすべての水平線を見つけます。これらの行を MyHSisters というコレクションに追加します。

    d. タプル MyHSisters と MyVSisters をグランド コレクションに追加します。

  4. リターングランドコレクション。

まだ回答としてマークしていません。どちらが最良の答えであるかを決定する前に、両方の人からの応答を待ち、3 つすべてのパフォーマンスと精度を比較します。

于 2012-09-15T08:09:51.080 に答える