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

可在迭代过程中发生变异的可迭代集合

可在迭代过程中发生变异的可迭代集合

千万里不及你 2021-09-12 14:27:41
是否有可以迭代的 Java(和 C#,如果你知道)的集合数据结构,具有以下属性:可以删除当前元素而不影响当前迭代器(已经启动的迭代器的其余迭代)。可以添加新元素,但也不会影响当前迭代器——当当前迭代器的迭代仍在进行时,不会作为迭代值包含在内。在我的情况下,每次迭代只会添加一个新元素,但在从可迭代对象中获取新迭代器之前,不会看到任何元素。元素的顺序无关紧要。实际上,有一个传入列表和一个传出项目列表。传入列表被迭代,一些被复制到一个新列表。一些新元素可以在迭代过程中添加到新列表中。迭代结束后,旧的传入列表被新的传出列表替换。整个过程本身就在一个循环中。因此,与具有这些添加/删除属性的元素相比,每次将元素复制到新构造的集合对象似乎效率低下。我正在考虑某种队列,让我可以预览当前项目,然后将其出列或不出列,然后移至下一个项目。我可以将更多项目添加到队列的头部,但不会看到它们,因为我正在接近尾端。双向链表可以具有这些属性,对吗?如果您真的想知道它的用途,那就是在我的回答中补充第二个大代码块。
查看完整描述

3 回答

?
SMILET

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

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


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>上使用可能更清洁。


查看完整回答
反对 回复 2021-09-12
?
宝慕林4294392

TA贡献2021条经验 获得超8个赞

在 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,并且在当前索引之前不要更改任何内容(从阅读您的要求来看,不需要)。


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


查看完整回答
反对 回复 2021-09-12
?
慕容708150

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

在 Java 中有CopyOnWriteArrayList可以执行您想要的操作:每次更改任何内容时,它都会创建一个后备数组的副本。但这确实意味着一旦开始迭代,任何迭代都是“一成不变的”,因此您可以随意删除/添加基础集合,而不会影响任何正在运行的迭代器。


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


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

    public Iterator<T> iterator() {

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

    }

}

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


这是你的问题的真正问题:


上面的代码会不时地制作底层数据存储的副本(我上面的代码片段每次制作迭代器时都会这样做。CopyOnWriteArrayList每次调用remove()or时都会这样做add())。“复制底层数据存储”操作需要O(n)时间,因为对于两倍大的列表,它需要两倍的时间。


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


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


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


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


查看完整回答
反对 回复 2021-09-12
  • 3 回答
  • 0 关注
  • 179 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信