是否有任何优雅的方式来遍历元素位置可以更改的列表?

Is there any elegant way of iterating through a list whose elements' positions can change?

本文关键字:列表 位置 遍历 任何优 方式 是否 元素      更新时间:2023-10-16

我目前遇到了一个令人作呕的问题。假设有一个对象列表(我们称之为对象类型(,我想遍历它。基本上,代码是这样的:

for(int i = 0; i < aList.Size(); ++i)
{
aList[i].DoSth();
}

这里困难的部分是,DoSth((方法可以更改调用者在列表中的位置!因此,可能会出现两种后果:首先,迭代可能永远无法结束;其次,可能会跳过某些元素(迭代不一定像上面一样,因为它可能是链表(。当然,第一个是主要问题。

必须通过以下约束来解决问题:

1(不能排除进行换仓操作的可能性;

2(换仓操作可以延迟到迭代完成,如果必要且可行;

3(由于它经常发生,因此只能对迭代进行最小的修改(因此不建议创建列表副本之类的操作(。

我使用的语言是C++,但我认为在 JAVA 和 C# 等中也有类似的问题。


以下是我尝试过的:

a( 尝试在迭代期间禁止换仓操作。但是,这涉及太多的客户端代码文件,查找和修改所有这些文件是不切实际的。

b( 修改 Object 的每个方法(例如,Method(((,这些方法可以改变自身的位置,并且将由DoSth((直接或间接调用,这样:首先我们可以知道aList正在进行迭代,我们将相应地处理Method((。如果迭代正在进行中,那么我们延迟Method((想要做的事情;否则,它现在就做它想做的事。这里的问题是:在这里延迟函数调用的最佳(易于使用,但足够高效(的方法是什么?Method((的参数可能相当复杂。此外,这种方法还将涉及相当多的功能!

c( 尝试修改迭代过程。我在这里遇到的实际情况非常复杂,因为它涉及两层迭代:第一层是普通数组迭代,而第二层是位于递归函数中的典型链表迭代。目前,对于第二层迭代,我能做的最好的事情就是限制其迭代时间并防止同一元素多次迭代。

所以我想有更好的方法来解决这个问题吗?也许一些很棒的数据结构会有所帮助?

你的问题在细节上有点轻,但从你写的内容来看,你似乎犯了混合关注的错误。

您的对象可能会执行某些操作,导致它继续存在或不存在。它不应该再存在的决定与实际将其存储在容器中的决定是分开的。

因此,让我们将这些担忧分开:

#include <vector>
enum class ActionResult {
Dies,
Lives,
};
struct Object
{
ActionResult performAction();
};
using Container = std::vector<Object>;
void actions(Container& cont)
{
for (auto first = begin(cont), last = end(cont)
; first != last
; )
{
auto result = first->performAction();
switch(result)
{
case ActionResult::Dies:
first = cont.erase(first);  // object wants to die so remove it
break;
case ActionResult::Lives:       // object wants to live to continue
++first;
break;
}
}
}

如果操作确实只有两个结果,生和死,那么我们可以习惯性地表达这个迭代:

#include <algorithm>
// ...
void actions(Container& cont)
{
auto actionResultsInDeath = [](Object& o)
{
auto result = o.performAction();
return result == ActionResult::Dies;
}; 
cont.erase(remove_if(begin(cont), end(cont), 
actionResultsInDeath),
end(cont));
}

嗯,问题解决了,至少就我现在感兴趣的情况而言。在我的情况下,aList实际上是一个链表,Object元素是通过指针访问的。如果aList的大小相对较小,那么我们有一个优雅的解决方案,就像这样:

Object::DoSthBig()
{
Object* pNext = GetNext();
if(pNext)
pNext->DoSthBig();
DoSth();
}

这有一个潜在的假设,即每个pNext在此过程中保持有效。但是,如果元素删除操作已经谨慎处理,那么一切都很好。

当然,这是一个非常特殊的例子,不能应用于其他情况。