我有一个包含数千个矩形的列表。我正在尝试通过删除位置在其他项目x像素以内的项目来减小列表的大小。
到目前为止,我最大的尝试是:
list = list.GroupBy(x => x.Location).Select(x => x.First()).ToList();
,但这只会删除完全匹配的内容。我希望删除所有合理相似的内容。有想法吗?
谢谢!
答案 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
}
}