I have a List containing a few thousand rectangles. I'm trying to reduce the size of the list by removing items whose location is within x pixels of other items.
So far, my best attempt has been:
list = list.GroupBy(x => x.Location).Select(x => x.First()).ToList();
but this only removes exact matches. I'd love to remove all that are reasonably similar. Thoughts?
Thanks!
Trying (as well as I can) to follow @EricLippert's sage advice, and think about the data structures first.
WLOG, assume Location
contains a Windows.System.Point
. We create a couple of handy extensions to help us later:
public static class PointExt {
public static double Distance(this Point p1, Point p2) => (p1-p2).Length;
public static Point PointZero = new Point(0, 0);
}
Now we can define our Rectangle
type (simplified for this problem):
public class Rectangle {
public string Site;
public Point Loc;
public Rectangle() { }
public Rectangle(string site, Point loc) {
Site = site;
Loc = loc;
}
}
NOTE: Site
is just to help in testing.
Now what we want to create are groups of Rectangle
s that are all close to each other. I choose to define close as meaning within 5 units of the center of the ( Location
s of the) group, found by averaging the current group's members.
So we can create a RectangleGroup
class to help us with this definition:
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();
}
I implemented IEnumerable
on the group to make it possible to use LINQ with a RectangleGroup
.
Using our tools in RectangleGroup
, we can create a RectangleGroups
class which manages a collection of RectangleGroup
s similar to Lookup
. This makes me think a (very) generic version of GroupBy
could be created that delegates the group membership to a type could be useful, and would make this class unecessary.
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();
}
With these classes available, reducing the List<Rectangle>
to just each Rectangle
nearest the center of each group is trivial:
var ans = new RectangleGroups(list).Select(lg => lg.Middle());
I think you're on the right track. Think about how you're generating keys for the group by. Maybe generate keys based on close-ness rather than exact match.
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;
}
You can define a custom comparison function and use it in the GroupBy clause.
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
}
}
This article goes into great detail: https://dotnetcodr.com/2014/06/20/grouping-elements-in-linq-net-using-groupby-and-an-equalitycomparer/
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.