可迭代集合,可在迭代过程中进行突变

时间:2018-09-10 07:55:25

标签: java c# collections iteration

在Java(还有C#)中是否存在可以迭代的具有以下属性的集合数据结构:

  • 可以删除当前元素,而不会影响当前的迭代器(已启动的迭代器的其余迭代)。
  • 可以添加新元素,但也不会影响当前的迭代器-当当前迭代器的迭代仍在进行时,不将其作为迭代值包括在内。在我的情况下,每次迭代只会添加一个新元素,但是在从迭代器中获取新的迭代器之前,不应看到任何新元素。
  • 元素的顺序无关紧要。

实际上,存在项目的传入列表和传出列表。迭代传入列表,并将其中一些复制到新列表。可以在迭代过程中将一些新元素添加到新列表中。迭代结束后,旧的传入列表将替换为新的传出列表。整个过程本身就是一个循环。

因此,与具有这些添加/删除属性的元素相比,每次将元素复制到新构造的集合对象中似乎效率低下。

我当时在想某种队列,可以让我预览当前项目,然后选择是否使它出队,然后移至下一个项目。而且我可以将更多项目添加到队列的开头,但由于我正在移到末尾,因此看不到它们。双链列表可以具有这些属性,对吧?

如果您真的想知道它的用途,那是在an answer of mine中增加第二个大代码块。

3 个答案:

答案 0 :(得分:1)

在C#中,使用call_doit: subq $24, %rsp leaq 12(%rsp), %rdi movl $0, 12(%rsp) call doit addq $24, %rsp ret List<T>而不是for (...)很容易:

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

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

答案 1 :(得分:1)

对于C#,您可以像在C语言中一样使用LinkedList<T>

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>。但是,标准List<E>上的ListIterator<E>可能更干净。

答案 2 :(得分:1)

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

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

public class ConstantIterationArrayList<T> extends ArrayList<T> {
    public Iterator<T> iterator() {
        return new ArrayList<T>(this).iterator();
    }
}

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

这是您遇到的真正问题:

以上内容会不时复制基础数据存储区(上面的代码段在您每次创建迭代器时都会这样做。CopyOnWriteArrayList在您每次调用remove()或{{1 }})。操作“复制基础数据存储”花费的时间为 O(n),例如,对于两倍大的列表,花费的时间是原来的两倍。

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

幸运的是,现代CPU具有相当大的缓存,并且可以在缓存页面中极快地工作。转换为:尽管实际上复制数据的效率很低,但实际上,只要支持数组适合页面内大约一个页面,它的速度就会比基于remove()的数据存储快许多语义。我们正在谈论的是〜1000个元素的取舍。 (请注意,通常,您对LinkedList所做的几乎所有操作都是 O(n),其中LinkedList往往可以与现代CPU体系结构很好地配合,ArrayList往往做得很差。关键是:LinkedList也很少是正确的答案!)

因此,如果此列表中的项数不超过1000个,我将继续使用LinkedList或上面为您编写的自定义类。

但是,如果您的个数更多,则CopyOnWriteArrayList不是在此处使用的正确数据存储。即使您暂时忘记了不断迭代的需求,在大型数组列表上调用ArrayList是个坏主意(除非将其移到列表的末尾)。在这种情况下,我将精确地勾勒出您需要针对此数据类型执行哪些操作,以及确实需要快速进行哪些操作,一旦有了完整的列表,请尝试查找完全适合您需求的集合类型,然后在(可能)情况下,没有什么比这更完美的匹配了,那就自己动手。像上面一样,当您必须滚动自己的数据类型时,通常最好让现有数据类型完成大部分工作,因此要么扩展现有数据类型,要么封装一个数据类型。