我想最好的方法是使用字典,尽管您必须对任何一种方法进行基准测试,但我选择了排序字典。
免责声明:我只做了最少的测试,可能完全错误。
给定
public static class Extensions
{
public static SortedDictionary<TKey, TValue> ToSortedDictionary<TKey,TValue>(this Dictionary<TKey, TValue> existing) => new(existing);
}
实施
public static IEnumerable<Rectangle> Enumerate(SortedDictionary<int, int[]> xs, SortedDictionary<int, int[]> ys)
{
// iterate the x dictionary
foreach (var (x1, value) in xs)
{
// get all y values
foreach (var y1 in value)
{
// does the Y dictionary contain the y ?
if(!ys.TryGetValue(y1, out var x2s)) continue;
// get all xs where is greater then the original
foreach (var x2 in x2s.Where(xa => xa > x1))
{
// check if x exists in x dictionary
if (!xs.TryGetValue(x2, out var y2s)) continue;
foreach (var y2 in y2s.Where(ya => ya >y1))
{
if (!ys.ContainsKey(y2)) continue;
yield return new Rectangle(x1, y1, x2 - x1, y2 - y1);
break;
}
}
}
}
}
用法
var points = new Point[]
{
new(1, 1),
new(1, 2),
new(1, 3),
new(2, 3),
new(3, 3),
new(3, 1),
new(3, 2),
new(3, 3),
};
var xs = points
.GroupBy(p => p.X, p => p.Y)
.ToDictionary(g => g.Key, g => g.ToArray())
.ToSortedDictionary();
var ys = points
.ToLookup(p => p.Y, p => p.X)
.ToDictionary(g => g.Key, g => g.ToArray())
.ToSortedDictionary();
Console.Write(string.Join(Environment.NewLine, Enumerate(xs, ys)));
输出
{X=1,Y=1,Width=2,Height=2}
{X=1,Y=2,Width=2,Height=1}
Full Demo Here
基准测试
总结
速度取决于您希望找到多少个矩形,在这种情况下,我使用的范围是 0..10000 x/y。我相信你可以更快地得到它。它在内存和速度上并没有完全线性扩展,但绝对不是二次时间复杂度或更糟。
注意我已经包含了一个粗略的并行版本,你期望找到的矩形越多,它会运行得越好,在这个化身中,并行版本可以在我的系统上大约 80 毫秒内完成 100,000 个点
环境
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1237 (21H1/May2021Update)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.401
[Host] : .NET 5.0.10 (5.0.1021.41214), X64 RyuJIT [AttachedDebugger]
.NET 5.0 : .NET 5.0.10 (5.0.1021.41214), X64 RyuJIT
Job=.NET 5.0 Runtime=.NET 5.0
结果
| Method |
N |
Mean |
Error |
StdDev |
Allocated |
| Single |
100 |
62.02 us |
0.841 us |
0.745 us |
76 KB |
| Para |
100 |
119.63 us |
1.375 us |
1.286 us |
165 KB |
| Single |
1000 |
827.67 us |
16.159 us |
15.870 us |
733 KB |
| Para |
1000 |
652.92 us |
12.901 us |
28.317 us |
959 KB |
| Single |
10000 |
11,761.19 us |
220.978 us |
415.050 us |
5,454 KB |
| Para |
10000 |
7,281.76 us |
148.422 us |
430.598 us |
6,591 KB |
| Single |
100000 |
260,526.78 us |
4,947.544 us |
5,293.817 us |
63,186 KB |
| Para |
100000 |
80,518.47 us |
1,595.902 us |
3,150.154 us |
72,006 KB |
| Single |
1000000 |
23,062,589.42 us |
424,680.649 us |
397,246.542 us |
4,342,997 KB |
| Para |
1000000 |
4,907,088.21 us |
33,959.608 us |
30,104.308 us |
5,127,048 KB |
完整的测试代码
[SimpleJob(RuntimeMoniker.Net50)]
[MemoryDiagnoser()]
public class Points
{
private Point[] _points;
[Params(100, 1000, 10000, 100000, 1000000)] public int N;
[GlobalSetup]
public void GetPoints()
{
var r = new Random(32);
_points = Enumerable
.Range(0, N).Select(x =>
new Point(
r.Next(10000),
r.Next(10000)))
.ToArray();
}
public static IEnumerable<Rectangle> Enumerate(SortedDictionary<int, int[]> xs, SortedDictionary<int, int[]> ys)
{
// iterate the x dictionary
foreach (var (x1, value) in xs)
foreach (var y1 in value) // get all y values
{
// does the Y dictionary contain the y ?
if (!ys.TryGetValue(y1, out var x2s)) continue;
// get all xs where is greater then the original
foreach (var x2 in x2s.Where(xa => xa > x1))
{
// check if x exists in x dictionary
if (!xs.TryGetValue(x2, out var y2s)) continue;
foreach (var y2 in y2s.Where(ya => ya > y1))
{
if (!ys.ContainsKey(y2)) continue;
yield return new Rectangle(x1, y1, x2 - x1, y2 - y1);
break;
}
}
}
}
public static IEnumerable<Rectangle> EnumerateParallel(SortedDictionary<int, int[]> xs, SortedDictionary<int, int[]> ys)
{
IEnumerable<Rectangle> Rectangles(int[] value, int x1)
{
foreach (var y1 in value) // get all y values
{
// does the Y dictionary contain the y ?
if (!ys.TryGetValue(y1, out var x2s)) continue;
// get all xs where is greater then the original
foreach (var x2 in x2s.Where(xa => xa > x1))
{
// check if x exists in x dictionary
if (!xs.TryGetValue(x2, out var y2s)) continue;
foreach (var y2 in y2s.Where(ya => ya > y1))
{
if (!ys.ContainsKey(y2)) continue;
yield return new Rectangle(x1, y1, x2 - x1, y2 - y1);
break;
}
}
}
}
return xs.AsParallel().AsUnordered().SelectMany(x => Rectangles(x.Value, x.Key));
}
[Benchmark]
public Rectangle[] Single()
{
var xs = _points
.GroupBy(p => p.X, p => p.Y)
.ToDictionary(g => g.Key, g => g.ToArray())
.ToSortedDictionary();
var ys = _points
.ToLookup(p => p.Y, p => p.X)
.ToDictionary(g => g.Key, g => g.ToArray())
.ToSortedDictionary();
return Enumerate(xs, ys)
.ToArray();
}
[Benchmark]
public Rectangle[] Para()
{
var task1 = Task.Run(() => _points
.GroupBy(p => p.X, p => p.Y)
.ToDictionary(g => g.Key, g => g.ToArray())
.ToSortedDictionary());
var task2 = Task.Run(() =>_points
.ToLookup(p => p.Y, p => p.X)
.ToDictionary(g => g.Key, g => g.ToArray())
.ToSortedDictionary());
return EnumerateParallel(task1.Result, task2.Result)
.ToArray();
}
}