从列表中删除类似的矩形

时间:2018-09-20 17:08:51

标签: c# .net linq

我有一个包含数千个矩形的列表。我正在尝试通过删除位置在其他项目x像素以内的项目来减小列表的大小。

到目前为止,我最大的尝试是:

list = list.GroupBy(x => x.Location).Select(x => x.First()).ToList();

,但这只会删除完全匹配的内容。我希望删除所有合理相似的内容。有想法吗?

谢谢!

3 个答案:

答案 0 :(得分:2)

尝试(尽我所能)遵循@EricLippert的明智建议,并首先考虑数据结构。

WLOG,假设Location包含Windows.System.Point。我们创建了几个方便的扩展程序,以供日后使用:

public static class PointExt {
    public static double Distance(this Point p1, Point p2) => (p1-p2).Length;
    public static Point PointZero = new Point(0, 0);
}

现在我们可以定义我们的Rectangle类型(为此问题简化):

public class Rectangle {
    public string Site;
    public Point Loc;

    public Rectangle() { }

    public Rectangle(string site, Point loc) {
        Site = site;
        Loc = loc;
    }
}

注意:Site只是为了帮助测试。

现在我们要创建的是Rectangle组,它们彼此靠近。我选择将“ close”定义为在({Location s)组中心的5个单位之内,这是通过对当前组的成员取平均值而得出的。

因此,我们可以创建一个RectangleGroup类来帮助我们进行以下定义:

public class RectangleGroup : IEnumerable<Rectangle> {
    List<Rectangle> members;
    Point center;

    public RectangleGroup() {
        members = new List<Rectangle>();
    }

    public RectangleGroup Add(Rectangle r) {
        members.Add(r);
        center = new Point(members.Average(m => m.Loc.X), members.Average(m => m.Loc.Y));
        return this;
    }

    public bool BelongsToGroup(Rectangle r) => center.Distance(r.Loc) <= 5;

    public Rectangle Middle() => members.OrderBy(m => m.Loc.Distance(center)).First();

    public IEnumerator<Rectangle> GetEnumerator() => members.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

我在该组上实现了IEnumerable,以便可以将LINQ与RectangleGroup一起使用。

使用RectangleGroup中的工具,我们可以创建一个RectangleGroups类,该类管理与RectangleGroup类似的Lookup的集合。这使我认为可以创建GroupBy的(非常)通用版本,该版本将组成员身份委派为一种类型可能会有用,并且会使该类变得不必要。

public class RectangleGroups : IEnumerable<RectangleGroup> {
    List<RectangleGroup> groups;
    public RectangleGroups() {
        init();
    }

    public RectangleGroups(IEnumerable<Rectangle> rs) {
        init();

        foreach (var r in rs.OrderBy(r => r.Loc.Distance(PointExt.PointZero)))
            Add(r);
    }

    private void init() {
        groups = new List<RectangleGroup>();
    }

    public void Add(Rectangle r) {
        var found = false;
        foreach (var g in groups) {
            found = g.BelongsToGroup(r);
            if (found) {
                g.Add(r);
                break;
            }
        }
        if (!found)
            groups.Add(new LocationGroup().Add(r));
    }

    public IEnumerator<LocationGroup> GetEnumerator() => groups.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

有了这些可用的类,将List<Rectangle>减少到仅靠近每个组中心的每个Rectangle就是微不足道的:

var ans = new RectangleGroups(list).Select(lg => lg.Middle());

答案 1 :(得分:1)

我认为您在正确的轨道上。考虑一下如何为分组依据生成密钥。也许是根据接近度而不是完全匹配来生成密钥。

list = list.GroupBy(x => ApproximateLocation(x.Location)).Select(x => x.First()).ToList();


public Location ApproximateLocation(Location original)
{
  int precision = 5;
  Location result = new Location{
    X = (original.X / precision) * precision;
    Y = (original.Y / precision) * precision;

  };
  return result;
}

答案 2 :(得分:0)

您可以定义一个自定义比较函数,并在GroupBy子句中使用它。

using System.Collections.Generic;
using System.Linq;

list.GroupBy(x => x.Location, new CustomComparer())...

public class CustomComparer : IEqualityComparer<LocationType>
{
    public bool Equals(LocationType A, LocationType B)
    {
        //return true if A is close enough to B
    }
}

本文非常详细:https://dotnetcodr.com/2014/06/20/grouping-elements-in-linq-net-using-groupby-and-an-equalitycomparer/