为了账号安全,请及时绑定邮箱和手机立即绑定

可以在迭代期间发生变异的可迭代集合

可以在迭代期间发生变异的可迭代集合

C#
ABOUTYOU 2022-01-09 17:30:17
Java(如果你知道的话,还有 C#)中是否有一个可以迭代的集合数据结构,具有以下属性:可以删除当前元素而不影响当前迭代器(已启动的迭代器的其余迭代)。可以添加新元素,但也不会影响当前迭代器——在当前迭代器的迭代仍在进行时,不会将其作为迭代值包含在内。在我的例子中,每次迭代只会添加一个新元素,但在从可迭代对象中获取新迭代器之前,不会看到任何新元素。元素的顺序无关紧要。实际上,有一个传入列表和一个传出项目列表。传入的列表被迭代,一些被复制到一个新的列表中。一些新元素可以在迭代期间添加到新列表中。迭代结束后,旧的传入列表被新的传出列表替换。这整个过程本身就是一个循环。因此,与具有这些添加/删除属性的集合对象相比,每次将元素复制到新构建的集合对象似乎效率低下。我在想某种队列,它可以让我预览当前项目,然后要么出队,要么不退出,然后移动到下一个项目。而且我可以将更多项目添加到队列的头部,但不会看到它们,因为我正在向最后移动。双向链表可以具有这些属性,对吗?如果您真的想知道它的用途,那就是在我的答案中增加第二个大代码块。
查看完整描述

3 回答

?
波斯汪

TA贡献1811条经验 获得超4个赞


在 C# 中,这很容易使用List<T>andfor (...)而不是foreach (...):


using System;

using System.Collections.Generic;

using System.Linq;


namespace Demo

{

    static class Program

    {

        static void Main()

        {

            List<int> list = Enumerable.Range(1, 10).ToList();


            for (int i = 0; i < list.Count; ++i)

            {

                if ((list[i] % 3) == 0) // Remove multiples of 3.

                    list.RemoveAt(i--); // NOTE: Post-decrement i

                else if ((list[i] % 4) == 0) // At each multiple of 4, add (2*value+1)

                    list.Add(list[i] * 2 + 1);

                else

                    ; // Do nothing.

            }


            Console.WriteLine(string.Join(", ", list)); // Outputs 1, 2, 4, 5, 7, 8, 10, 17

        }

    }

}

这里的关键是使用索引而不是foreach,并且在当前索引之前不要更改任何内容(从阅读您的要求来看,不需要)。


但是,如果您确实需要在当前索引之前添加或删除元素,那么这种方法就不起作用(或者至少,它变得更加复杂)。


查看完整回答
反对 回复 2022-01-09
?
ibeautiful

TA贡献1993条经验 获得超5个赞

对于 C#,您可以LinkedList<T>像在好的 ol' C 中一样使用:


public DoStuff<T>(LinkedList<T> list)

{

    var node = list.First;


    while(node != null)

    {

        // do stuff with node


        node = node.Next;

    }

}

该node是类型LinkedListNode<T>。您可以使用 访问该值node.Value,使用 删除list.Remove(node)。对于T elem你也有list.AddAfter(node, elem),list.AddBefore(node, elem),list.AddFirst(elem)和list.AddLast(elem)。所有这些操作都是 O(1)。你可以用它做各种迭代,如果你只想迭代原始元素,然后在做任何事情之前缓存下一个节点并记住最后一个节点:


var lastNode = list.Last;

var node = list.First;


while(node != lastNode.Next)

{

    var nextNode = node.Next;


    // do stuff with node


    node = nextNode;

}

Java 中的等价数据结构也称为LinkedList<E>. 但是,ListIterator<E>使用标准List<E>可能更清洁。


查看完整回答
反对 回复 2022-01-09
?
牧羊人nacy

TA贡献1862条经验 获得超7个赞

在 java 中有CopyOnWriteArrayList 可以满足您的要求:每次更改任何内容时,它都会复制支持数组。但这确实意味着一旦您开始迭代,任何迭代都是“一成不变的”,因此您可以随意删除/添加到底层集合,而不会影响任何正在运行的迭代器。


您还可以构建自己的具有此行为的集合类型。这将是一个 3 班轮:


public class ConstantIterationArrayList<T> extends ArrayList<T> {

    public Iterator<T> iterator() {

        return new ArrayList<T>(this).iterator();

    }

}

(上面创建了列表的副本,然后为您提供了副本的迭代器,从而方便地确保对该列表的任何修改绝对不会对该迭代器产生影响)。


这是您问题的真正问题:


以上将不时复制底层数据存储(我上面的代码片段每次创建迭代器时都会这样做。CopyOnWriteArrayList每次调用remove()或时都会这样做add())。“复制底层数据存储”操作需要O(n)时间,例如,两倍大的列表需要两倍的时间。


ArrayList通常remove(),除非您要删除列表末尾或非常接近末尾的元素,否则该操作的属性是O(n)操作:如果列表是列表的两倍,则从列表中删除元素需要两倍的时间大。


幸运的是,现代 CPU 具有相当大的缓存,并且可以在缓存页面内以极快的速度运行。翻译为:尽管数据复制过的感觉效率不高,在实践中,只要在页面左右的时间内支持数组的配合,这是很多快于基于数据存储的LinkedList语义。我们正在谈论最多约 1000 个元素的给予或接受。(请注意,一般来说,您对 a 所做的几乎所有事情LinkedList都是O(n)并且在ArrayList现代 CPU 架构中往往能很好地工作,但LinkedList往往做得很差。关键是:LinkedList也很少是正确的答案!)


因此,如果您在此列表中的项目不超过约 1000 项,我会继续使用CopyOnWriteArrayList我在上面为您编写的自定义类。


但是,如果你有更多的比,ArrayList是不正确的数据存储在这里使用。即使您暂时忘记了不断迭代的需求;调用remove()大型数组列表是一个坏主意(除非删除非常接近列表的末尾)。在这种情况下,我会准确地勾勒出您需要对该数据类型执行哪些操作以及哪些操作真正需要快速,一旦您有了完整的列表,请尝试找到一个完全符合您需求的集合类型,并且在(可能的)情况下,没有任何特定的完美匹配,自己做一个。像上面一样,当您必须滚动自己的数据类型时,让现有数据类型完成大部分工作通常是个好主意,因此要么扩展现有数据类型,要么封装一个。


查看完整回答
反对 回复 2022-01-09
  • 3 回答
  • 0 关注
  • 182 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号