cad.net 并行前投影分块
投影分块
根据桶排序可以知道,分桶的边界之后可以并行.
所以这就给我们制作区间容器一个思路.
首先SortedList是有序KV结构,每次加入都会排序.
(微软提供的有序类型都是不允许重复key,所以自带二分法也没有找最左最右,需要自己写.)
我们把它的key作为区间min值
,value作为区间max值
+容器.
区间表示CAD图元包围盒影子,左边缘和右边缘投影到X轴.
那么同一个区间是不是有可能碰撞,而不同区间肯定就是不碰撞的.
重点是加入分裂和重组:
每次加入都判断重叠
0x01,如果重叠就把区间max
更新,把容器合并.
0x02,如果原有key在新插入Min Max
之间,
需要更新key,但是key不能改的,
就记录原有容器后,移除key,再加入新插入.
通过这种动态分块的方式,最后得到一份投影在x轴的区间表,
区间就是分块
就可以并行执行各自任务.
要怎么才能把y轴也分块呢?就需要改用哈希网格了?
可以放到这里在线运行看看效果,手机也能跑耶.
https://www.json.cn/run/csharp
https://www.onlinegdb.com/online_csharp_compiler
代码
using System;
using System.Collections.Generic;
using System.Linq;
// 区间容器
public class RangeMap<T> where T : IEquatable<T> {
// 内部数据容器
private class Chunk {
public int Max { get; set; }
// 这里元素唯一的,即使不是,也会在碰撞时候消除.
public List<T> Data { get; set; } = new List<T>();
// 碰撞链
public List<List<T>> Link = new List<List<T>>();
// 分块之后内部只需要串行任务
// 只有碰撞的才是链条
public void CollisionsLink(Func<T, T, bool> isCollision) {
// 排序后消除碰撞才更快.
Data.Sort();
var indices = new HashSet<int>();
var currentSet = new HashSet<int>();
for (int i = Data.Count - 1; i >= 0; i--) {
if (indices.Contains(i)) continue;
for (int j = i - 1; j >= 0; j--) {
if (indices.Contains(j)) continue;
if (!isCollision(Data[i], Data[j])) continue;
currentSet.Add(i);
currentSet.Add(j);
indices.Add(i);
indices.Add(j);
}
if (currentSet.Count == 0) continue;
Link.Add(currentSet.Select(item => Data[item]).ToList());
currentSet.Clear();
}
// Data.Clear();
}
// 分块之后内部只需要串行任务
// 全部都是链条,碰撞的会归位一条链
public void CollisionsAll(Func<T, T, bool> isCollision) {
// 排序后消除碰撞才更快.
Data.Sort();
var indices = new HashSet<int>();
var currentSet = new HashSet<int>();
for (int i = Data.Count - 1; i >= 0; i--) {
if (indices.Contains(i)) continue;
currentSet.Add(i);
indices.Add(i);
for (int j = i - 1; j >= 0; j--) {
if (indices.Contains(j)) continue;
if (!isCollision(Data[i], Data[j])) continue;
currentSet.Add(j);
indices.Add(j);
}
Link.Add(currentSet.Select(item => Data[item]).ToList());
currentSet.Clear();
}
// Data.Clear();
}
}
// 区间有序容器,就可以并行每个容器任务.
private SortedList<int, Chunk> rangeList = new SortedList<int, Chunk>();
// 因为是有序容器,使用二分法找到目标
public void Add(int newStart, int newEnd, List<T> newValues) {
int left = 0, right = rangeList.Count - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
// 原有记录
var existingRange = rangeList.Values[mid];
int existingStart = rangeList.Keys[mid];
int existingEnd = existingRange.Max;
// 插入前判断投影碰撞区域
// 0x01,有碰撞加入原有容器中,并更新max
if (existingStart <= newEnd && newStart <= existingEnd) {
existingRange.Max = Math.Max(existingRange.Max, newEnd);
foreach (var val in newValues) {
existingRange.Data.Add(val);
}
return;
} else if (newStart < existingStart) {
// 0x02,插入min比key小的情况,移除原有记录,重新添加
var tempRange = rangeList[existingStart];
rangeList.Remove(existingStart);
// 比原有范围大,就能递归到0x03加入.
Add(newStart, Math.Max(newEnd, tempRange.Max), newValues);
// 原有范围插入,就能递归到0x01合并容器.
Add(existingStart, tempRange.Max, tempRange.Data);
return;
} else if (newStart > existingEnd) {
left = mid + 1;
} else {
right = mid - 1;
}
}
// 0x03,没有重叠,直接添加
rangeList.Add(newStart, new Chunk { Max = newEnd, Data = newValues });
}
public void Add(HashSet<T> set) {
if (set.Count == 0) return;
int minValue = int.MaxValue;
int maxValue = int.MinValue;
// CAD这里改为求包围盒
foreach (var element in set) {
// int currentValue = Convert.ToInt32(element); 居然是银行家算法
int currentValue = (int)(double)(object)element;
minValue = Math.Min(minValue, currentValue);
maxValue = Math.Max(maxValue, currentValue);
}
Add(minValue, maxValue, set.ToList());
// Console.WriteLine($"小{minValue}, 大{maxValue}");
}
// 并行
public void CollisionsLink(Func<T, T, bool> isCollision) {
rangeList.Values.AsParallel()
.WithDegreeOfParallelism(System.Environment.ProcessorCount)
.ForAll(v => v.CollisionsLink(isCollision));
}
public void CollisionsAll(Func<T, T, bool> isCollision) {
rangeList.Values.AsParallel()
.WithDegreeOfParallelism(System.Environment.ProcessorCount)
.ForAll(v => v.CollisionsAll(isCollision));
}
public void PrintLink() {
foreach (var item in rangeList) {
Console.WriteLine($"起始值: {item.Key}, 结束值: {item.Value.Max}");
foreach(var item2 in item.Value.Link)
Console.WriteLine($"链条: {string.Join(", ", item2)}");
}
}
// 用于输出当前管理的所有范围及对应值信息的方法
public void Print() {
foreach (var item in rangeList) {
Console.WriteLine($"起始值: {item.Key}, 结束值: {item.Value.Max}, 值列表: {string.Join(", ", item.Value.Data)}");
}
}
}
class Program {
static void Main() {
// CAD这个泛型是ObjectId来的,我用double模拟包围盒而已.
RangeMap<double> map = new RangeMap<double>();
map.Add(new HashSet<double> { 1.1, 5.9 });
map.Add(new HashSet<double> { 3.1, 8.9 });
map.Add(new HashSet<double> { 10.1, 15.9 });
map.Add(new HashSet<double> { 2.1, 6.9 });
map.Add(new HashSet<double> { 16.1, 20.9 });
map.Add(new HashSet<double> { 4.1, 12.9 });
map.Add(new HashSet<double> { 7.1, 9.9 });
map.Add(new HashSet<double> { 21.1, 22.9 });
map.Print();
Console.WriteLine("==========");
map.CollisionsLink((a,b) => Math.Abs(a-b) < 1);
map.PrintLink();
Console.WriteLine("==========");
map.CollisionsAll((a,b) => Math.Abs(a-b) < 1);
map.PrintLink();
}
}