cad.net 排序
行式排序
// 容差需要set,而Point3d的无法set,直接暴露字段或者封装到构造函数内.
public class XYZD {
public double X;
public double Y;
public double Z;
public ObjectId ObjectId;
public XYZD(double x, double y, double z, ObjectId id, double tol = 0.1) {
if (tol <= 0)
throw new ArgumentException("XYZD Tolerance <= zero.");
if (double.IsNaN(tol))
throw new ArgumentException("XYZD Tolerance is NaN.");
if (double.IsInfinity(tol))
throw new ArgumentException("XYZD Tolerance is Infinity.");
// 公差修正
X = RoundToNearest(x, tol);
Y = RoundToNearest(y, tol);
Z = RoundToNearest(z, tol);
ObjectId = id;
}
// 公差修正数值的方法
double RoundToNearest(double value, double tol) {
return Math.Round(value / tol) * tol;
}
// 内部比较器写法
public int CompareTo(XYZD other) {
// 如果X相同,比较Y,如果Y也相同,比较Z
if (this.X != other.X) return this.X.CompareTo(other.X);
if (this.Y != other.Y) return this.Y.CompareTo(other.Y);
return this.Z.CompareTo(other.Z);
}
}
// 外部补充比较器写法
public class XYZDComparer : IComparer<XYZD> {
public int Compare(XYZD a, XYZD b) {
// 如果X相同,比较Y,如果Y也相同,比较Z
if (a.X != b.X) return a.X.CompareTo(b.X);
if (a.Y != b.Y) return a.Y.CompareTo(b.Y);
return a.Z.CompareTo(b.Z);
}
}
// 调用:
public List<XYZD> SortDbPoints(List<ObjectId> ids) {
var tr = DBTrans.Top;
List<XYZD> list = new();
for(int i = 0; i<ids.Count; i++) {
using var ent = tr.GetObject<DBPoint>(ids[i], OpenMode.ForRead);
if(ent is null) continue;
list.Add(new(ent.Position.X, ent.Position.Y, ent.Position.Z, ids[i]);
}
// 1 内部比较器,它只会修改原有数组
list.Sort();
/*
// 2 外部比较器需要new,它只会修改原有数组
// 先行后列,先列后行...比较是千变万化的,因而外部设计其实比较好
list.Sort(new XYZDComparer());
// 3 使用Linq进行排序,它会生成新的数组.
list = list.OrderBy(item => item.X)
.ThenBy(item => item.Y)
.ThenBy(item => item.Z)
.ToList();
*/
return list;
}
列式排序
这应该是最快的方法了,
原理是准备多个共同长度的数组,它们垂直对齐,因而叫列式数据.
利用快排算法把全部数组一起比较和交换.
1,结构是SOA,当排序完数组后,利用SIMD或者SIMT矩阵运算.
全部+1.0不就是平移吗.
2,第一个数组是有序的,可以使用二分法Array.BinarySearch
,
其他数组是依附排序的,不过这个自带的二分法没有很好的处理重复元素.
3,两个XYZD类型比较时候是大量比较xList,触发CPU预读数组效果好.
对比两份图是否一致,快速找到不同位置,检查两个数组是否一样,
就是xList1[i]==xList2[i]
,就可以粗过滤,而yList就是细过滤.
4,储存的时候需要按照磁盘4k(4096byte)对齐,4k就是512个double(8字节/个).
(linux预读文件是一次128K,并可调整,win没有资料啊)
512/3=170.66,多出来不用(放校验码)
每个XYZD
就是一页,然后Link<XYZD>
就表示多页,
因为非连续结构在磁盘上面也是有意义的,
修改不需要全部写入,而是写入一个页并调整索引表就好了.
其他优化方向:
非托管数组1
非托管数组2
循环展开和SIMD
public class XYZD<T> {
enum SortOrder {
None, // 无排序模式
Asc, // 升序,从小到大
Des // 降序,从大到小
}
class MyData {
// 维度,载入顺序有优先级
public List<List<double>> Mult = new(3); //限定Capacity
// 排序方式
public List<SortOrder> SortOrders = new(3);
public int Count => SortOrders.Count;
public int InfoCount => Mult.Count>0 ? Mult[0].Count : 0;
public MyData Add(List<double> p, SortOrder so){
Mult.Add(p);
SortOrders.Add(so);
return this;
}
}
/*
// 暴露成只读的数组视图
public ReadOnlyCollection<double> X => _xList.AsReadOnly();
public ReadOnlyCollection<double> Y => _yList.AsReadOnly();
public ReadOnlyCollection<double> Z => _zList.AsReadOnly();
*/
public List<double> _xList = [];
public List<double> _yList = [];
public List<double> _zList = [];
public List<T> _idList = [];
double _tol;
bool _removeTrace = false; // 消除微量偏差
public XYZD(double tol = 0.1, bool removeTrace = false) {
if (tol <= 0)
throw new ArgumentException("XYZD Tolerance <= zero.");
if (double.IsNaN(tol))
throw new ArgumentException("XYZD Tolerance is NaN.");
if (double.IsInfinity(tol))
throw new ArgumentException("XYZD Tolerance is Infinity.");
_tol = tol;
_removeTrace = removeTrace;
}
public XYZD(List<Point3d> pts, List<T> ids,
double tol = 0.1, bool removeTrace = false) :this(tol, removeTrace) {
if(pts.Count != ids.Count)
throw new ArgumentException("XYZD 你做咩唔对齐啊");
// 这里应该写一个循环展开进行批量插入.
for(int i = 0; i < ids.Count; i++) {
/*
// 公差修正,可以不写在这里,而是线性比较的时候顺便处理,还可以减少除法器.
_xList.Add(RoundToNearest(pts[i].X, _tol));
_yList.Add(RoundToNearest(pts[i].Y, _tol));
_zList.Add(RoundToNearest(pts[i].Z, _tol));
*/
_xList.Add(pts[i].X);
_yList.Add(pts[i].Y);
_zList.Add(pts[i].Z);
_idList.Add(ids[i]);
}
}
// 加入时候展开数据,使得内部可以替换Point字段
public void Add(Point3d pt, T id) {
_xList.Add(pt.X);
_yList.Add(pt.Y);
_zList.Add(pt.Z);
_idList.Add(id);
}
// 01下上分行,行左起
public void SortRowToBTLR() {
var md = new MyData()
.Add(_yList, SortOrder.Asc)
.Add(_xList, SortOrder.Asc)
.Add(_zList, SortOrder.Asc);
Sort(md, 0, 0, md.InfoCount - 1);
}
// 02下上分行,行右起
public void SortRowToBTRL() {
var md = new MyData()
.Add(_yList, SortOrder.Asc)
.Add(_xList, SortOrder.Des)
.Add(_zList, SortOrder.Asc);
Sort(md, 0, 0, md.InfoCount - 1);
}
// 03上下分行,行右起(图像和01一样,只是逆序)
public void SortRowToTBRL() {
var md = new MyData()
.Add(_yList, SortOrder.Des)
.Add(_xList, SortOrder.Des)
.Add(_zList, SortOrder.Asc);
Sort(md, 0, 0, md.InfoCount - 1);
}
// 04上下分行,行左起
public void SortRowToTBLR() {
var md = new MyData()
.Add(_yList, SortOrder.Des)
.Add(_xList, SortOrder.Asc)
.Add(_zList, SortOrder.Asc);
Sort(md, 0, 0, md.InfoCount - 1);
}
// 05左右分列,列下起
public void SortColumnToLRBT() {
var md = new MyData()
.Add(_xList, SortOrder.Asc)
.Add(_yList, SortOrder.Asc)
.Add(_zList, SortOrder.Asc);
Sort(md, 0, 0, md.InfoCount - 1);
}
// 06左右分列,列上起
public void SortColumnToLRTB() {
var md = new MyData()
.Add(_xList, SortOrder.Asc)
.Add(_yList, SortOrder.Des)
.Add(_zList, SortOrder.Asc);
Sort(md, 0, 0, md.InfoCount - 1);
}
// 07右左分列,列上起(图像和05一样,只是逆序)
public void SortColumnToRLTB() {
var md = new MyData()
.Add(_xList, SortOrder.Des)
.Add(_yList, SortOrder.Des)
.Add(_zList, SortOrder.Asc);
Sort(md, 0, 0, md.InfoCount - 1);
}
// 08右左分列,列下起
public void SortColumnToRLBT() {
var md = new MyData()
.Add(_xList, SortOrder.Des)
.Add(_yList, SortOrder.Asc)
.Add(_zList, SortOrder.Asc);
Sort(md, 0, 0, md.InfoCount - 1);
}
/// <summary>
/// 排序
/// </summary>
/// <param name="md">数据集</param>
/// <param name="mdIndex">md索引,指定排序子集</param>
/// <param name="start">排序的起始位置</param>
/// <param name="end">排序的结束位置</param>
void Sort(MyData md, int mdIndex, int start, int end) {
if (end == start) return; // 一个就不需要排序了
if (end - start < 0) throw new ("错误索引");
if (md.InfoCount-1 - end < 0) throw new ("错误索引超出");
var so = md.SortOrders[mdIndex];
if (so == SortOrder.None) return;
var xyzList = md.Mult[mdIndex];
var des = so == SortOrder.Des; // 降序
QuickSort(xyzList, start, end, des);
// 排序完之后,同X排序Y,同Y排序Z
// 连续相同范围,起始索引
int sameIndexStart = -1;
// 运行中判断循环可能编译器没有循环展开和SIMD优化,
// 我这么写只是为了demo更简短,最好应该独立写出正序循环和倒序循环
// 排序方式翻转顺序,降序就倒序遍历
if (des) (start,end)=(end,start);
// 步长
int step = des ? -1 : 1;
for (int i = start; i != end; i += step) {
int sp = i + step;
// 因为已经排序和倒序循环,都是大-小,不需要abs的
var sub = xyzList[sp] - xyzList[i];
if (sub > _tol) {
if (sameIndexStart != -1) {
// 行内排序
int sameIndexEnd = i;
// 索引保持左小右大,让排序方式进行翻转
if (sameIndexStart > sameIndexEnd){
(sameIndexStart,sameIndexEnd) = (sameIndexEnd,sameIndexStart);
}
Sort(md, mdIndex+1/*下一个排序数组*/, sameIndexStart, sameIndexEnd);
sameIndexStart = -1;
}
continue;
}
// 抹平微量偏差,斜线变成阶梯
if (_removeTrace)
xyzList[sp] = xyzList[i];
if (sameIndexStart == -1)
sameIndexStart = i;
}
// 处理最后一个区间
if (sameIndexStart != -1) {
int sameIndexEnd = end;
if (sameIndexStart > sameIndexEnd){
(sameIndexStart,sameIndexEnd) = (sameIndexEnd,sameIndexStart);
}
Sort(md, mdIndex + 1, sameIndexStart, sameIndexEnd);
sameIndexStart = -1;
}
}
// 快排算法
void QuickSort(List<double> list, int low, int high, bool descending) {
if (low < high) {
int pi = Partition(list, low, high, descending);
QuickSort(list, low, pi - 1, descending);
QuickSort(list, pi + 1, high, descending);
}
}
#if NET45_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
int Partition(List<double> list, int low, int high, bool descending) {
var pivot = list[high];
int i = low - 1;
for (int j = low; j < high; j++) {
// 这里存在相等条件,所以必须要大于小于
if (descending ? list[j] > pivot : list[j] < pivot) {
i++;
Swap(_xList, i, j);
Swap(_yList, i, j);
Swap(_zList, i, j);
Swap(_idList, i, j);
}
}
Swap(_xList, i+1, high);
Swap(_yList, i+1, high);
Swap(_zList, i+1, high);
Swap(_idList, i+1, high);
return i + 1;
}
#if NET45_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
// 元组交换
void Swap<T>(List<T> list, int index1, int index2) {
(list[index1], list[index2]) = (list[index2], list[index1]);
}
}
命令测试
[CommandMethod(nameof(TestCmdXYZD))]
public void TestCmdXYZD()
{
var pso = new PromptSelectionOptions();
var ssPsr = Env.Editor.GetSelection(pso);
if (ssPsr.Status != PromptStatus.OK) return;
using DBTrans tr = new();
XYZD<ObjectId> xyzd = new(1.0); // 公差数值
var ids = ssPsr.Value.GetObjectIds();
for (int i = 0; i < ids.Length; i++)
{
using var ent = tr.GetObject<DBPoint>(ids[i], OpenMode.ForRead);
if(ent is null) continue;
xyzd.Add(ent.Position, ids[i]);
}
// 利用多段线查看排序
if (xyzd._xList.Count < 2) return;
xyzd.SortRowToBTLR();
PolylinePrint(xyzd, 1);
xyzd.SortRowToBTRL();
PolylinePrint(xyzd, 2);
xyzd.SortRowToTBRL();
PolylinePrint(xyzd, 3);
xyzd.SortRowToTBLR();
PolylinePrint(xyzd, 4);
xyzd.SortColumnToLRBT();
PolylinePrint(xyzd, 5);
xyzd.SortColumnToLRTB();
PolylinePrint(xyzd, 6);
xyzd.SortColumnToRLTB();
PolylinePrint(xyzd, 7);
xyzd.SortColumnToRLBT();
PolylinePrint(xyzd, 8);
}
void PolylinePrint<T>(XYZD<T> xyzd, int color) {
var tr = DBTrans.Top;
// 多段线绘制排序路径
Polyline pl = new();
pl.SetDatabaseDefaults();
pl.ColorIndex = color;
for (int i = 0; i < xyzd._xList.Count; i++)
pl.AddVertexAt(i, new Point2d(xyzd._xList[i], xyzd._yList[i]), 0, 0, 0);
tr.CurrentSpace.AddEntity(pl);
// 用一个圆标注起点,方便测试
Circle circle = new();
circle.Center = new Point3d(xyzd._xList[0], xyzd._yList[0], 0); // 起点
circle.Radius = 20;
circle.ColorIndex = color;
tr.CurrentSpace.AddEntity(circle);
// 逆序的线看不出差别,用文字数据来区别.
StringBuilder sb = new();
for (int i = 0; i < xyzd._xList.Count; i++)
sb.AppendLine($"({xyzd._xList[i]},{xyzd._yList[i]},{xyzd._zList[i]})");
Env.Printl(sb.ToString());
Env.Printl("**********************");
}
合并循环
因为存在运行时判断,因此这种方式C++编译器估计会,
无法循环展开和没有自动SIMD优化,
最好还是写成两个正序和倒序循环.
using System;
class Program {
static void Main() {
int[] array = { 1,2,3 };
Console.WriteLine("true表示降序,false表示升序");
bool des = true;
int start = 0;
int end = array.Length - 1;
// 步长
int step = des? -1 : 1;
if (des) {
int temp = start;
start = end;
end = temp;
}
Console.WriteLine();
Console.WriteLine("合并1:");
// 按照设定的顺序(正序或倒序)遍历数组元素并输出
// 遍历每个
for (int i = start; i != end + step; i += step)
Console.Write($"({array[i]}) ");
Console.WriteLine();
// 两个两个
for (int i = start; i != end; i += step)
Console.WriteLine($"({array[i]}, {array[i + step]}) ");
Console.WriteLine();
Console.WriteLine("正序遍历数组:");
for (int i = 0; i < array.Length; i++)
Console.WriteLine(array[i]);
Console.WriteLine("倒序遍历数组:");
for (int i = array.Length - 1; i > -1; i--)
Console.WriteLine(array[i]);
Console.WriteLine("合并2:");
start = 0;
end = array.Length;
if (des) {
(start,end)=(end,start);
start--;
end--;
}
for (int i = start; des?i>end:i<end; i += step) {
Console.WriteLine(array[i]);
}
}
}
输出结果:
(3) (2) (1)
(3, 2) (2, 1)
块排测试,从小到大
using System;
public class Program
{
public static void Main(string[] args)
{
int[] arr = { 3, 6, 8, 10, 1, 2, 1 };
QuickSort(arr, 0, arr.Length - 1);
foreach (var item in arr)
{
Console.Write(item + " ");
}
}
public static void QuickSort(int[] arr, int low, int high)
{
if (low < high)
{
int pi = Partition(arr, low, high);
QuickSort(arr, low, pi - 1); // Before pi
QuickSort(arr, pi + 1, high); // After pi
}
}
private static int Partition(int[] arr, int low, int high)
{
int pivot = arr[high];
int i = (low - 1);
for (int j = low; j < high; j++)
{
if (arr[j] < pivot)
{
i++;
// Swap arr[i] and arr[j]
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// Swap arr[i+1] and arr[high] (or pivot)
int temp2 = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp2;
return i + 1;
}
}
快排测试,从大到小
using System;
public class Program
{
public static void Main()
{
int[] arr = { 3, 6, 8, 10, 1, 2, 1 };
QuickSortDescending(arr, 0, arr.Length - 1);
foreach (var item in arr)
{
Console.Write(item + " ");
}
}
private static void QuickSortDescending(int[] arr, int left, int right)
{
if (left < right)
{
int pivotIndex = PartitionDescending(arr, left, right);
QuickSortDescending(arr, left, pivotIndex - 1);
QuickSortDescending(arr, pivotIndex + 1, right);
}
}
private static int PartitionDescending(int[] arr, int left, int right)
{
int pivot = arr[right]; // 选择最右侧的元素作为基准
int i = left - 1;
for (int j = left; j < right; j++)
{
if (arr[j] > pivot) // 降序排序,只有这里不同,是大于
{
i++;
// 交换arr[i]和arr[j]
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 交换arr[i+1]和arr[right](基准值)
int temp2 = arr[i + 1];
arr[i + 1] = arr[right];
arr[right] = temp2;
return i + 1; // 返回基准值的最终位置
}
}
树排序
红黑树,AVL树,跳表,B树家族(B树/B+树/BW树/LSM树)
C#的有序结构都是不能重复的
SortedSet/SortedList/SortedDictionary
public class Point3dComparer : IComparer<Point3d> {
public int Compare(Point3d a, Point3d b) {
// 容差一致
if (a.IsEqualTo(b, Tolerance.Global)) return 0;
// 如果X相同,比较Y,如果Y也相同,比较Z
if (a.X != b.X) return a.X.CompareTo(b.X);
if (a.Y != b.Y) return a.Y.CompareTo(b.Y);
return a.Z.CompareTo(b.Z);
}
}
// 调用
SortedSet set = new();
set.Add(new Point3d(1,1,1));
set.Add(new Point3d(21,1,1));
set.Add(new Point3d(1,1,1));
组合数
把int32分成三块,32/3=10bit,多出来2bit不用.
[空][X][Y][Z]
这样就能同X比较Y,同Y比较Z.
能利用上><=比较了.
缺点: 局限精度,10bit表达范围太少了
public struct Point3d {
public override int GetCode() {
// 公差值用于四舍五入
double tol = 0.0001;
// 四舍五入X, Y, Z到最接近的整数
int roundedX = (int)Math.Round(X / tol) * (int)tol;
int roundedY = (int)Math.Round(Y / tol) * (int)tol;
int roundedZ = (int)Math.Round(Z / tol) * (int)tol;
// 组合数
return = roundedX << 20 | roundedY << 10 | roundedZ;
}
}
公差原理
using System;
class Program
{
static void Main()
{
// 千位舍入的例子
double value1 = 2011;
double tol1 = 1000;
double roundedValue1 = RoundToNearest(value1, tol1);
Console.WriteLine($"{value1} rounded to the nearest {tol1} is {roundedValue1}");
// 输出:2011 rounded to the nearest 1000 is 2000
// 小数点后一位舍入的例子
double value2 = 1.5;
double tol2 = 0.1;
double roundedValue2 = RoundToNearest(value2, tol2);
Console.WriteLine($"{value2} rounded to the nearest {tol2} is {roundedValue2}");
// 输出:1.5 rounded to the nearest 0.1 is 1.5
// 更多测试
double value3 = 1234.5678;
double tol3 = 0.05; // 保留两位小数
double roundedValue3 = RoundToNearest(value3, tol3);
Console.WriteLine($"{value3} rounded to the nearest {tol3} is {roundedValue3}");
// 输出:1234.5678 rounded to the nearest 0.05 is 1234.60
}
// 公差修正数值的方法
static double RoundToNearest(double value, double tol)
{
if (tol == 0)
throw new ArgumentException("Tolerance cannot be zero.");
return Math.Round(value / tol) * tol;
}
}
数据结构
为了加速我们用一些有序结构和轻量级结构:
StaticLinkedList
静态链表插入和删除速度非常快.
using System;
public class StaticLinkedList {
private Node[] list;
private int headIndex;
private const int EMPTY_FLAG = -2;
// 链表节点
struct Node {
public int data;
public int next;
}
// 初始化链表长度,创建指定长度的节点数组,
// 并对链表头和各节点的初始状态进行设置
public StaticLinkedList(int length) {
list = new Node[length];
headIndex = 0;
list[headIndex].next = -1;
// 初始化所有节点为空闲状态(用-2表示空闲)
for (int i = 0; i < length; i++) {
list[i].next = EMPTY_FLAG;
}
}
// 插入一个新节点,
// 先找到链表末尾位置,
// 然后查找空闲节点插入给定值的新节点,
// 若插入成功则返回新节点在数组中的索引,
// 若链表已满则返回 -1
public int Insert(int value) {
int i = headIndex;
while (list[i].next!= -1) {
i = list[i].next;
}
int newIndex = FindEmptyIndex();
if (newIndex == -1) {
Console.WriteLine("链表已满,无法插入");
return -1;
}
list[newIndex].data = value;
list[newIndex].next = list[i].next;
list[i].next = newIndex;
return newIndex;
}
// 查找返回链表中处于空闲状态的节点索引,
// 若没有空闲节点则返回 -1
private int FindEmptyIndex() {
for (int i = 0; i < list.Length; i++) {
if (list[i].next == EMPTY_FLAG) {
list[i].next = -1;
return i;
}
}
return -1;
}
// 遍历整个静态链表,从链表头开始,
// 顺着next指针依次输出每个节点的数据域值,
// 直到next指针为 -1(表示链表末尾)
public void Traverse() {
int current = list[headIndex].next;
while (current!= -1) {
Console.Write(list[current].data + " ");
current = list[current].next;
}
Console.WriteLine();
}
// 查找值为target的节点,
// 若找到则返回其在链表中的位置索引(从0开始计数)
// 若未找到则返回 -1
public int Search(int target) {
int current = list[headIndex].next;
int index = 0;
while (current!= -1) {
if (list[current].data == target) {
return index;
}
index++;
current = list[current].next;
}
return -1;
}
// 删除值为target的节点,
// 通过遍历链表查找目标节点,
// 找到后调整指针来移除该节点,并将该节点设置为空闲状态,
// 若成功删除返回true,否则返回false
public bool Delete(int target) {
int prev = headIndex;
int current = list[headIndex].next;
while (current!= -1) {
if (list[current].data == target) {
list[prev].next = list[current].next;
list[current].next = EMPTY_FLAG;
return true;
}
prev = current;
current = list[current].next;
}
return false;
}
}
class Program {
static void Main() {
StaticLinkedList staticLinkedList = new(10);
staticLinkedList.Insert(5);
staticLinkedList.Insert(10);
staticLinkedList.Insert(15);
staticLinkedList.Traverse();
staticLinkedList.Delete(10);
staticLinkedList.Traverse();
int searchIndex = staticLinkedList.Search(10);
if (searchIndex!= -1) {
Console.WriteLine($"值10在静态链表中的索引是: {searchIndex}");
}
else {
Console.WriteLine("未找到指定值");
}
}
}
有序不重复
官方C#自带结构都是不允许重复的
SortedSet/SortedList/SortedDictionary
获取范围不是O(logN)吗?高版本才有? https://cloud.tencent.com/developer/ask/sof/109384237
// var act = new Actuator(null,Sequence.StartFirst);
// var acts = _set.GetViewBetween(act, act);
using System;
using System.Collections.Generic;
class Program {
class MyNumber {
public int Value { get; set; }
public MyNumber(int value) { Value = value; }
}
class Comparer1 : IComparer<MyNumber> {
public int Compare(MyNumber x, MyNumber y) {
return x.Value.CompareTo(y.Value);
}
}
static void Main() {
var s = new SortedSet<MyNumber>(new Comparer1());
s.Add(new MyNumber(6));
s.Add(new MyNumber(2));
s.Add(new MyNumber(3));
s.Add(new MyNumber(3));
s.Add(new MyNumber(4));
s.Add(new MyNumber(5));
foreach (MyNumber num in s) {
Console.Write(num.Value + " ");
}
}
}
有序重复
官方的二分法BinarySearch有问题,
只会返回首次出现,如果重复就会搜错位置,
需要再向左遍历,否则插入丢失的情况.
既然如此不如自己写一个二分法,
二分法用-1开始会令代码变得短少.
改用不安全数组会更好...貌似也没有必要优化那么极限.
官方之所以没写这种数据结构,估计是因为插入之后再快排会更快.
并且不可重复的,可以通过SortedList和SortedDictionary结构的value进行收集.
但是链式选择时候,需要[ab,bc,cd,de]进行(i+1)/2确定位置.
using System;
using System.Collections;
using System.Collections.Generic;
public class OrderList<T> : IEnumerable<T> {
private List<T> _list = new List<T>();
private IComparer<T> _cmp;
public int Count => _list.Count;
public OrderList() { _cmp = null; }
public OrderList(IComparer<T> cmp = null) { _cmp = cmp; }
IEnumerator IEnumerable.GetEnumerator() {
return ((IEnumerable<T>)_list).GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator() {
return _list.GetEnumerator();
}
// 二分法找最左边(修复了官方的无法找重复)
public int FindFirst(T item) {
int left = -1; int right = _list.Count;
while (left + 1 < right) {
int mid = left + (right - left) / 2;
int compareResult = _cmp != null ?
_cmp.Compare(item, _list[mid]) :
Comparer<T>.Default.Compare(item, _list[mid]);
if (compareResult == 0) right = mid;
else if (compareResult < 0) right = mid;
else left = mid;
}
int tempLeft = left + 1;
while (tempLeft - 1 >= 0 && (_cmp != null ?
_cmp.Compare(item, _list[tempLeft - 1]) == 0 :
Comparer<T>.Default.Compare(item, _list[tempLeft - 1]) == 0)) {
tempLeft--;
}
return tempLeft;
}
// 二分法找最右边
public int FindLast(T item) {
int left = -1; int right = _list.Count;
while (left + 1 < right) {
int mid = left + (right - left) / 2;
int compareResult = _cmp != null ?
_cmp.Compare(item, _list[mid]) :
Comparer<T>.Default.Compare(item, _list[mid]);
if (compareResult == 0) right = mid;
else if (compareResult < 0) right = mid;
else left = mid;
}
int tempRight = right - 1;
while (tempRight + 1 < _list.Count && (_cmp != null ?
_cmp.Compare(item, _list[tempRight + 1]) == 0 :
Comparer<T>.Default.Compare(item, _list[tempRight + 1]) == 0)) {
tempRight++;
}
return tempRight;
}
public void Add(T item) {
var right = FindFirst(item);
_list.Insert(right, item);
}
public void RemoveAt(int index) {
if (index >= _list.Count)
throw new IndexOutOfRangeException($"索引 {index} 超出了列表的有效范围");
_list.RemoveAt(index);
}
public bool Remove(T item) {
var left = FindFirst(item);
var right = FindLast(item);
if (left == -1 || right == _list.Count || left > right) {
return false;
}
for (int i = right; i >= left; i--) {
_list.RemoveAt(i);
}
return true;
}
// 自带Contains是O(n),这个是O(log2(n))
public bool Contains(T item) {
var mid = FindFirst(item);
int compareResult = _cmp != null ?
_cmp.Compare(item, _list[mid]) :
Comparer<T>.Default.Compare(item, _list[mid]);
return compareResult == 0;
}
// 为了之后能用上SIMD,所以此处不要封装迭代器
public List<T> GetList() {
return _list;
}
public T this[int index] {
get {
if (index < 0 || index >= _list.Count)
throw new IndexOutOfRangeException($"索引 {index} 超出了列表的有效范围");
return _list[index];
}
set {
if (index < 0 || index >= _list.Count)
throw new IndexOutOfRangeException($"索引 {index} 超出了列表的有效范围");
_list[index] = value;
}
}
}
class Program {
static void Main() {
OrderList<int> orderList = new OrderList<int>();
orderList.Add(5);
orderList.Add(6);
orderList.Add(55);
orderList.Add(3);
orderList.Add(3);
orderList.Add(3);
orderList.Add(9);
orderList.Add(6);
orderList.GetList().ForEach(a=>Console.Write(a + " "));
Console.WriteLine("");
var left = orderList.FindFirst(3); // 重复
var right = orderList.FindLast(3);
Console.WriteLine($"重复 Left {left}, Right {right}");
var left2 = orderList.FindFirst(5); // 唯一
var right2 = orderList.FindLast(5);
Console.WriteLine($"唯一 Left {left2}, Right {right2}");
var none = orderList.FindFirst(1); // 找不到,会指示插入位置是0;
Console.WriteLine($"找不到插入位置: {none}");
Console.WriteLine($"找不到标识: {orderList.Contains(1)}");
orderList.Remove(1);
orderList.GetList().ForEach(a=>Console.Write(a + " "));
Console.WriteLine("");
orderList.Remove(3);
orderList.GetList().ForEach(a=>Console.Write(a + " "));
Console.WriteLine("");
}
}
SimdList
由于List