【问题标题】:Extending ArrayList and Implementing IEnumerable - Is there a better way?扩展 ArrayList 并实现 IEnumerable - 有没有更好的方法?
【发布时间】:2014-11-30 01:33:36
【问题描述】:

我需要一种数据类型,它实现了 IEnumerable 接口,但也允许在恒定时间内删除/插入任意数量的元素。

此外,数据类型必须允许对已知元素的恒定访问时间。基本上我需要的是一个实现 IEnumerable 的 C89 风格的双向链表。

我的一个要求是我不能多次迭代数据。

我考虑(并且正在努力)创建一个扩展 ArrayList 并实现 IEnumerable 接口的类,但我担心有一种更清洁或更好的方法。

这一切都是因为我正在处理的代码的输入变量之一是 IEnumerable 类型(我无法更改它)。

我的问题有两个:

  1. 有没有一种方法可以在不创建单独的类或迭代每个元素的情况下实现我所描述的功能?
  2. 如果对 1 的回答是否定的,那么在创建我所描述的类时我应该采取哪些广泛的步骤?

谢谢!

-------编辑-------

该算法的主要目标是根据特定标准(专有,因此没有代码,对不起!!!)将元素组合在一起。为此,该算法创建一个单独的列表,并且对于创建的每个“父”记录,复制(或(编辑)理想地移动,这更快!)由算法指定为父节点的子节点的记录。很抱歉对此没有更清楚;我昨晚在美国东部标准时间下午 2 点到 10 点工作。

【问题讨论】:

  • List<T> 不是已经提供你想要的了吗?
  • 不,如果你看到hereList.GetRange 需要n 时间来检索任意数量的元素;我将编辑问题以使该要求更清楚! :)
  • 如何拥有双向链表和恒定的元素访问时间?
  • Andrew,C89 将允许您直接移动下一个/上一个指针。即使您使用 sizeof 将指针移动到正确的位置,实现这是 C# 也需要使用 unsafe (请参阅我的答案)。此外,由于 C# LinkedList 没有 GetRange (并且不能,因为这将要求它跟踪索引)任何时候你想删除/复制 k 个元素,你必须使用 k 时间。对于整个列表,总和为 2N
  • 我读到你的编辑是想创建从父节点到子节点的引用。这不需要移动 复制任何实际元素,只需添加引用(再次假设引用类型)。如果你回到双向链表领域,那么是的,你说的很有道理。我想我还没有完全理解这个问题——我知道代码是专有的,但是匿名的代码示例可能会澄清一些事情。

标签: c# performance algorithm inheritance multiple-inheritance


【解决方案1】:

感谢 Enigmativity 告诉我这是不可能的。

由于索引要求元素保持有序并且不被删除,因此重新索引列表将需要 N-(N-k) 时间。 (其中 N 是列表的长度,k 是元素的数量)。使用严格链表解决方案仍然需要 k 时间来迭代所有 k 个元素。

我看到的唯一解决方案是实现一种使用 C89 样式的双向链表以及 sizeof 来跳过要删除的 k 个元素到下一个元素的类型(以保持列表的完整性)。这将允许 N 时间处理列表。它还需要 2N 内存。

值得指出的是,这种解决方案必须利用“不安全”C# 关键字。这意味着任何调用此方法的方法也必须标记为不安全,一直到 main。请参阅here,了解为什么这是一条相当糟糕的路线。

【讨论】:

  • 您可以在没有不安全的情况下执行此操作,如果您不存储指向linkedList 中内存位置的指针,而是将索引存储到支持数组/列表。不过,我看不出如何在恒定时间内删除多个元素。只要您之前没有从列表中删除任何内容,就可以跳过元素,但是一旦列表有间隙,您需要遍历所有要删除的元素,以便跳过之前删除的元素。
  • 在 C89 双向链表中,节点上有 back 和 next 指针。假设你有 a->b->c->d (也有反向指针)。将 a 的 next 指针设置为 d,并将 d 的后指针设置为 a 需要 2 次操作,或恒定时间。使用游标指针,其中 int* cur = &a; cur+=sizeof(MyNodeType)*numberEles; cur.back=&a; a.next=&cur;.这允许恒定时间删除/添加。
  • 在使用支持数组写出一个快速示例之后,您是正确的。这会起作用,但只能在我可以使用内存操作的环境中。否则,要确定列表何时找到要删除的元素的末尾,则需要 numberEle 的步骤。
  • 设置两个指针需要2次操作,true;但是找到“a”和“d”总是需要 n 次操作。需要从a到d遍历链表;您不能仅通过知道它们在内存中的大小来跳过它们之间的元素,因为在先前的删除或插入之后,这些元素在内存中不一定是连续的。我不知道如何使用内存操作来解决这个问题。
  • 如果你想返回一个List<t>,你必须创建一个你的自定义LinkedList的副本,从而迭代它(在LinkedList上,而不是在后备数组上)。如果你只想要一个IEnumerable<T>,那么你的LinkedList 只需要使用yield 来实现IEnumerable。使用这个 IEnumerable 的代码当然会导致对 LinkedList 的另一次迭代,除了迭代它之外,没有办法使用 IEnumerable。
【解决方案2】:

虽然它不满足您的恒定时间要求,但基于 B+ tree 的某些东西的 order statistic tree 将具有对数时间的插入和删除。查找元素的索引和按索引查找元素也是对数时间操作。 Google 上的 5 秒没有显示任何明显的订单统计树的开源 .NET 实现,但编写自己的 .NET 应该不会太难。您可能决定不严格使用 B+ 树来支持集合,但其想法是仍然可以进行顺序访问。

这样的类可以正确实现IList<T>,它当然是从IEnumerable<T> 派生的。听起来您知道IEnumerable<T> 本身对您的保证很少。如果您的代码传递了一个IEnumerable<T>,它实际上不是您的订单统计树类,那么您必须从IEnumerable<T> 创建订单统计树,(我认为)这是一个O(n ln n) 操作。我假设IEnumerable<T> 是有限的,这也是not guaranteed。不过,听起来您并不关心无限集合。除了IList<T> 的要求之外,您还需要为插入和删除添加特定的范围操作(如List<T>.AddRange(IEnumerable<T>)),以针对您的用例进行优化。

此解决方案的优点是不需要unsafe,并且它不直接提出@HugoRune 在他对您的回答的评论中提到的“但一旦列表有差距”的问题。

另外,不要费心从ArrayList 继承。如果您想使用 ArrayList(或任何其他集合实现)来存储您的项目,请通过委托给集合实现的内部实例来实现。

【讨论】:

  • 我最初考虑过这样的树,但考虑到我们在添加子节点后不需要重新访问每个节点,我无法证明迭代列表两次所需的时间是合理的。这虽然可行,但需要 2*n*log(n) 时间,这比 2*n 差:)
  • 您不一定需要对列表进行两次迭代,尽管您是正确的,最初构建树可能比您所追求的任何替代方案都更昂贵。
【解决方案3】:

我仍然不确定这个 c89 风格的链表应该做什么;在 O(1) 中插入和删除多个元素之类的某些事情绝对看起来是不可能的,除非在非常受限的情况下。

尽管如此,使用指针操作将这种数据结构转换为不带指针的托管 c# 结构很简单:分配一个数组并假装它的数组索引是指针。

所以而不是malloc(100 *Size_Of_Node)
你会做nodes[]= new Node[100];

在每个节点内部,您将存储int nextint prev,而不是Node* nextNode* prev,然后通过Node nextNode = nodes[currentNode.next] 访问相邻节点。

这允许您在访问每个元素时以额外的间接层为代价进行直接指针操作的所有可能性。

如果您使用 List<Node> 而不是数组作为后备存储,您还可以根据需要增加其大小,而不是预先分配它。

为了在需要 IEnumerable 的地方使用这个自定义的 C89 样式的链表,你需要做的就是为这个结构实现 IEnumerable,如下所示(不完整):

public class LinkedListC89<T> : IEnumerable<T>
{
   private class Node<T>
   {
      public int Prev;
      public int Next;
      public T Value;
   }
   private Node[] nodes;
   private Node first;

   public IEnumerator<T> GetEnumerator()
   {
        Node curNode = first;

        While (curNode != null)
        {
            yield return curNode.Value;
            if (curNode.Next>nodes.Length) yield break;
            curNode = nodes[curNode.Next]
        }
   }
}

【讨论】:

    猜你喜欢
    • 2019-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-19
    • 2019-03-17
    • 1970-01-01
    • 1970-01-01
    • 2011-10-23
    相关资源
    最近更新 更多