[英]C# Collection - Order by an element (Rotate)
我有一个IEnumerable<Point>
集合。 假设它包含 5 个点(实际上它更像是 2000)
我想对这个集合进行排序,以便集合中的一个特定点成为第一个元素,所以它基本上是在特定点切割一个集合并将它们重新连接在一起。
所以我的5点清单:
{0,0}, {10,0}, {10,10}, {5,5}, {0,10}
相对于索引 3 处的元素重新排序将变为:
{5,5}, {0,10}, {0,0}, {10,0}, {10,10}
解决这个问题的最有效的计算方法是什么,或者是否有一种已经存在的内置方法......如果是这样,我似乎找不到一个!
var list = new[] { 1, 2, 3, 4, 5 };
var rotated = list.Skip(3).Concat(list.Take(3));
// rotated is now {4, 5, 1, 2, 3}
在这种情况下,一个简单的数组副本是 O(n),对于几乎所有现实世界的目的来说,这应该足够了。 但是,我会同意您在某些情况下 - 如果这是多级算法深处的一部分 - 这可能是相关的。 此外,您是否只需要以有序的方式遍历这个集合或创建一个副本?
链表很容易像这样重新组织,尽管访问随机元素的成本会更高。 总体而言,计算效率还取决于您如何准确访问此项目集合(以及它们是什么类型的项目 - 值类型或引用类型?)。
标准的 .NET 链表似乎不支持这种手动操作,但一般来说,如果您有链表,您可以轻松地按照您描述的方式移动列表的各个部分,只需分配新的“下一个”和“上一个” " 指向端点的指针。
此处可用的收藏库支持此功能: http : //www.itu.dk/research/c5/ 。 具体来说,您正在寻找LinkedList<T>.Slide()
方法,您可以在LinkedList<T>.View()
返回的对象上使用该方法。
没有两次枚举list
版本,但由于T[]
导致更高的内存消耗:
public static IEnumerable<T> Rotate<T>(IEnumerable<T> source, int count)
{
int i = 0;
T[] temp = new T[count];
foreach (var item in source)
{
if (i < count)
{
temp[i] = item;
}
else
{
yield return item;
}
i++;
}
foreach (var item in temp)
{
yield return item;
}
}
[Test]
public void TestRotate()
{
var list = new[] { 1, 2, 3, 4, 5 };
var rotated = Rotate(list, 3);
Assert.That(rotated, Is.EqualTo(new[] { 4, 5, 1, 2, 3 }));
}
注意:添加参数检查。
ulrichb 显示的 Linq 方法的另一种替代方法是使用队列类(一个先进先出的集合)出列到您的索引,并将您取出的入列。
使用 linq 的幼稚实现是:
IEnumerable x = new[] { 1, 2, 3, 4 };
var tail = x.TakeWhile(i => i != 3);
var head = x.SkipWhile(i => i != 3);
var combined = head.Concat(tail); // is now 3, 4, 1, 2
此处发生的情况是,您执行了获得组合序列中第一个元素所需的两次比较。 该解决方案可读且紧凑,但效率不高。 其他贡献者描述的解决方案可能更有效,因为他们使用特殊的数据结构作为数组或列表。
您可以编写用户定义的 List 扩展,通过使用 List.Reverse() 进行旋转。 我从 C++ 标准模板库中获取了基本思想,它基本上分三步使用 Reverse: Reverse(first, mid) Reverse(mid, last) Reverse(first, last)
据我所知,这是最有效和最快的方式。 我测试了 10 亿个元素,旋转 Rotate(0, 50000, 800000) 需要 0.00097 秒。 (顺便说一句:向 List 添加 10 亿个整数已经需要 7.3 秒)
这是您可以使用的扩展名:
public static class Extensions
{
public static void Rotate(this List<int> me, int first, int mid, int last)
{
//indexes are zero based!
if (first >= mid || mid >= lastIndex)
return;
me.Reverse(first, mid - first + 1);
me.Reverse(mid + 1, last - mid);
me.Reverse(first, last - first + 1);
}
}
用法是这样的:
static void Main(string[] args)
{
List<int> iList = new List<int>{0,1,2,3,4,5};
Console.WriteLine("Before rotate:");
foreach (var item in iList)
{
Console.Write(item + " ");
}
Console.WriteLine();
int firstIndex = 0, midIndex = 2, lastIndex = 4;
iList.Rotate(firstIndex, midIndex, lastIndex);
Console.WriteLine($"After rotate {firstIndex}, {midIndex}, {lastIndex}:");
foreach (var item in iList)
{
Console.Write(item + " ");
}
Console.ReadKey();
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.