我目前遇到一个令人作呕的问题。假设有一个对象列表 aList (我们称之为 Object 的类型),我想对其进行迭代。基本上,代码是这样的:
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)尝试修改迭代过程。我在这里遇到的实际情况非常复杂,因为它涉及两层迭代:第一层是纯数组迭代,而第二层是位于递归函数中的典型链表迭代。目前,对于第二层迭代,我能做的最好的事情就是限制其迭代时间,并防止同一元素被多次迭代。
所以我想可能会有更好的方法来解决这个问题?也许一些很棒的数据结构会有所帮助?
答案 0 :(得分:5)
您的问题有点细节,但是从您写的内容看来,您犯了混合关注点的错误。
您的对象可能执行某些操作,导致该对象继续存在或不存在。决定不再存在该决定与将其实际存储在容器中的问题分开。
因此,让我们将这些关注点分开:
#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));
}
答案 1 :(得分:0)
好,问题解决了,至少就我现在感兴趣的情况而言。在我的情况下, aList 实际上是一个链表,并且 Object 元素是通过指针访问的。如果 aList 的大小较小,那么我们将有一个优雅的解决方案,如下所示:
Object::DoSthBig()
{
Object* pNext = GetNext();
if(pNext)
pNext->DoSthBig();
DoSth();
}
这有一个基本假设,即每个pNext在此过程中保持有效。但是,如果已经谨慎处理了元素删除操作,那么一切都很好。
当然,这是一个非常特殊的示例,无法应用于其他情况。