如何有效地对抽象列表进行嵌套迭代?

Posted

技术标签:

【中文标题】如何有效地对抽象列表进行嵌套迭代?【英文标题】:How to do a nested iteration over an abstract list efficiently? 【发布时间】:2018-11-17 02:30:12 【问题描述】:

我正在尝试复制“遍历自笛卡尔积的上对角矩阵”的典型构造,并以列表为源。用外行的话来说,如果我有一个数组 a 我想这样做:

for (int i = 0; i < a.length; i++) 
  for (int j = i; j < a.length; j++) 
    collect(a[i], a[j]);
  

但是有一个抽象的List,我不能依靠索引来有效地访问。

我考虑过这一点,它节省内存和时间(因为调用 sublist 使用原始列表作为支持结构)但对于 Java 来说看起来并不习惯:

for (List<E> tail = list; !tail.isEmpty(); tail = tail.sublist(1, tail.size()) 
  E a = tail.get(0);
  for (E b : tail) 
    collect(a, b);
  

还有更好的选择吗?


一个样本:

如果输入序列是 [1, 2, 3] 并且占位符 collectSystem.out.println,则输出应该是其中的对(索引,而不是值)i>= j

1 1
1 2
1 3
2 2
2 3
3 3

并且不是所有可能的对(可以通过两个简单的循环来完成)。

【问题讨论】:

“抽象列表”是什么意思? collect(..) 方法有什么作用?样本输入和预期输出是什么?这个问题对我来说很不清楚。 他有一个基类List的对象。根据列表的具体实现,按索引访问对象效率低下,例如LinkedList。据我了解,他正在寻找一种不依赖于将get(i) 用于i != 0 的解决方案。 您是否考虑过使用列表的迭代器@fortran? 看过答案后,我认为您当前的解决方案还不错。顺便说一句,您可以使用!tail.isEmpty() 而不是tail.size() &gt; 0 使其更清晰。 是的,都是因为在Java中,迭代器不能被复制,这很可惜。它们可以使用其他一些语言,例如 C++。 【参考方案1】:

您可以将listIterator 方法用于返回ListIterator 对象的列表,该对象将按顺序遍历您的列表。最重要的是,可以使用可选参数index 调用该方法,以从列表中的给定点开始。 ListIterator 也很方便地知道它的当前索引,我们可以使用它来设置我们的第二个迭代器。

一个例子可能如下所示:

List<Integer> list = new LinkedList<Integer>(Arrays.asList(1, 2, 3));

ListIterator<Integer> i = list.listIterator();

while (i.hasNext())

    ListIterator<Integer> j = list.listIterator(i.nextIndex());
    int iV = i.next();
    while (j.hasNext())
    
        collect(iV, j.next());
    

为以下对调用collect

1, 1
1, 2
1, 3
2, 2
2, 3
3, 3

正如正确提到的,这给我们留下了 list.listIterator(i.nextIndex())-call 的潜在复杂性 O(n)

因此,如果内存不是问题,另一种解决方案是确保您的 List 是一种易于随机访问的类型(例如,数组支持的列表,例如 ArrayList),并在这样的列表中复制您的数据不是这样的。

【讨论】:

调用 listIterator(int) 在链表上具有 o(n) 复杂性,不是最优的。 虽然这是真的,但如果不从头部或尾部开始,则无法到达LinkedList 中的某个点,除非将您的列表复制到通过随机访问更方便访问的结构中.或者好吧,我不知道。 因此,如果内存不是问题,请将您的结构复制到 ArrayList 并对其使用随机访问。复制时只需 O(n) 一次。 好吧,这违背了试图找到优雅解决方案的意义......如果我要这样做,我会将界面更改为需要RandomAccess 列表。 很难找到一个优雅的解决方案,因为LinkedList 在内存中的表示方式使得如果不从头一直遍历到某个值,就不可能“知道”某个值的位置或者尾巴……【参考方案2】:
public static void collect(List<Integer> data) 
    List<Integer> a = data instanceof RandomAccess ? data : new ArrayList<>(data);

    for (int i = 0; i < a.size(); i++)
        for (int j = i; j < a.size(); j++)
            collect(a.get(i), a.get(j));

【讨论】:

与@Ben 的回答相同的问题,使用任意索引调用listIterator @fortran 是的。我已经改变了解决方案。 您应该使用instanceof RandomAccess,而不是instanceof ArrayList。这是一个由 ArrayList “实现”的标记接口,但随机访问列表不仅仅是 ArrayList。 @DodgyCodeException 谢谢! 我一直在进行一些实验,这是第二快的方法(在复制到数组之后,速度稍快),所以令我惊讶的是,这是一个可以接受的解决方案 :)

以上是关于如何有效地对抽象列表进行嵌套迭代?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 lambda 流迭代嵌套列表?

python递归排序所有嵌套的iterable

如何使用 PHP 和 MySQL 有效地对大型数据集进行分页?

如何在 javascript 中最有效地对规范化数据进行非规范化

如何有效地对 SQL 数据库中的记录进行版本控制

追溯性地对应用程序进行版本控制