如何有效地对抽象列表进行嵌套迭代?
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]
并且占位符 collect
是 System.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() > 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 谢谢!
我一直在进行一些实验,这是第二快的方法(在复制到数组之后,速度稍快),所以令我惊讶的是,这是一个可以接受的解决方案 :)以上是关于如何有效地对抽象列表进行嵌套迭代?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 PHP 和 MySQL 有效地对大型数据集进行分页?