在std::list中合并(将两个项目融合在一起,用融合替换)项目的算法(即破坏性聚类)

Algorithm to merge (fuse two items together, replace them with the fusion) items in std::list (i.e. destructive clustering)

本文关键字:项目 融合 聚类 破坏性 算法 在一起 替换 两个 list std 合并      更新时间:2023-10-16

如果这很明显,我很抱歉,我是C++新手。stackoverflow上似乎有相关的答案,只是我对这些答案的理解不够,无法应用于我的案例。

我有一个表示视觉补丁的类实例列表。当特征之间的距离低于阈值时,我希望合并这些项,用合并的输出替换父项。

类似这样的东西:

  1. 使用嵌套for循环遍历所有项目(将每个项目与其他项目进行比较)

  2. 当找到匹配时(不是同一个实例):

    1. 从匹配的对中构造一个新的(子)实例,附加到新列表中。

    2. 从列表中删除两个(父)项目

  3. 继续迭代列表,查找其他匹配

  4. 将新列表附加到原始列表。

我知道如何使用迭代器在单个for循环中从列表中删除项目,但由于erase()递增到下一个项目,我不清楚它在嵌套循环中如何工作。

我可能还需要使这个函数递归,因为最终合并应该通过合并合并将列表减少为一组有代表性的实例。

如有建议,不胜感激。

以下是我的尝试,但不起作用(嵌套循环会干扰其中一个和另一个)。对列表中的元素进行这种成对比较的正确方法是什么

#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> mylist;
list<int>::iterator mylistiterOutter;
list<int>::iterator mylistiterInner;
for(int i=0; i<10; i++) {
    mylist.push_back(i);
    cout << i << endl;
}
for(int i=0; i<10; i++) {
    mylist.push_back(i);
    cout << i << endl;
}
int counter =0;
for(mylistiterOutter = mylist.begin(); mylistiterOutter != mylist.end();) {
    cout << "Size of mylist: " << mylist.size() << endl;
    for(mylistiterInner = mylist.begin(); mylistiterInner != mylist.end();) {
        cout << "mylistiterInner: " << *mylistiterInner << endl;
        cout << "mylistiterOutter: " << *mylistiterOutter << endl;
        //if (mylistiterOutter == mylistiterInner) {// match!
        if (false) {
            //mylistiterOutter = mylist.erase(mylistiterOutter);
            //mylistiterInner = mylist.erase(mylistiterInner);
        } else {
            mylistiterOutter++;
            mylistiterInner++;
        }
        counter++;
    }
}
cout << endl << "Size of mylist: " << mylist.size() << endl << "NumIterations: " << counter << endl;
return(0);
}

谢谢@lalitm。我先尝试了你的方法,因为它更接近我最初的设想,但J.N.的建议更优雅,所以我也会尝试。不幸的是,我无法让@lalitm的方法发挥作用。(导致分段故障)。下面是稍微复杂一点的代码,其中包括示例类和合并代码,使用@lalitm的方法:

#include <iostream>
#include <list>
#include <cmath>
using namespace std;
class percepUnit {
public:
    int cx, cy; // location of percept in frame
    bool remove; // used to delete percepts
    // constructor method
    percepUnit(int ix, int iy) {
        cx = ix;
        cy = iy;
        remove = false;
    }
};
bool canMerge(percepUnit unitA, percepUnit unitB) {
    double dist = sqrt(pow(abs(unitA.cx-unitB.cx),2)+ pow(abs(unitA.cy-unitB.cy),2));
    return (dist < 3);
}
percepUnit merge(percepUnit unitA, percepUnit unitB) {
    int x,y;
    x = unitA.cx+unitB.cx/2;
    y = unitA.cy+unitB.cy/2;
    return (percepUnit(x,y));
}
int main() {
    list<percepUnit> mylist;
    list<percepUnit> mergedlist;
    list<percepUnit>::iterator mylistiterOutter;
    list<percepUnit>::iterator mylistiterInner;
    bool mylistiterOutterChanged;
    mylist.push_back(percepUnit(0,0));
    mylist.push_back(percepUnit(2,2));
    mylist.push_back(percepUnit(5,5));
    mylist.push_back(percepUnit(7,7));
    //cout << "merge front/back? " << canMerge(mylist.front(),mylist.back()) << endl;
    //percepUnit test = merge(mylist.front(),mylist.back());
    //cout << "merged front/back (x,y): " << test.cx << "," << test.cy << endl;
    for(mylistiterOutter = mylist.begin(); mylistiterOutter != mylist.end();) {
    cout << "Size of mylist: " << mylist.size() << endl;
        mylistiterInner = mylistiterOutter;
        mylistiterOutterChanged = false;
        for (++mylistiterInner; mylistiterInner != mylist.end();) {
            if (canMerge(*mylistiterOutter, *mylistiterInner )) {
                mergedlist.push_back(merge(*mylistiterOutter, *mylistiterInner));
                mylistiterOutter = mylist.erase(mylistiterOutter);
                mylistiterInner = mylist.erase(mylistiterInner);
                mylistiterOutterChanged = true;
            } else {
               ++mylistiterInner;
            }
        }
        if (!mylistiterOutterChanged) {
            ++mylistiterOutter;
        }
    }
    mylist.splice(mylist.end(), mergedlist);

    return(0);
}

这是我的gdb信息:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7b31d97 in std::_List_node_base::unhook() ()
   from /usr/lib/libstdc++.so.6
(gdb) bt
#0  0x00007ffff7b31d97 in std::_List_node_base::unhook() ()
   from /usr/lib/libstdc++.so.6
#1  0x0000000000401786 in std::list<percepUnit, std::allocator<percepUnit> >::_M_erase (this=0x7fffffffe4d0, __position=...)
at /usr/include/c++/4.4/bits/stl_list.h:1424
#2  0x000000000040153d in std::list<percepUnit, std::allocator<percepUnit> >::erase (this=0x7fffffffe4d0, __position=...)
at /usr/include/c++/4.4/bits/list.tcc:111
#3  0x0000000000401130 in main () at debug.cpp:61

仍然没有运气。我认为问题可能是上面的代码没有测试两个迭代器是否指向列表中的同一项,因此会混淆迭代器(在不应该增加的情况下增加或不增加)。

如何测试两个迭代器是否指向同一项(没有比较所有类成员的蛮力?,但同一实例的两个副本不是同一实例。)

void mergeObjects(std::list<T>& list)
{
    std::list<T> new_list;
    typedef std::list<T>::iterator Itr;
    for (Itr i=list.begin(); i != list.end();)
    {
        Itr j=i;
        bool is_merged = false;
        for (++j; j != list.end();)
        {
            if (isMergeable(*i, *j))
            {
                T merged = mergeObj(*i, *j);
                new_list.push_back(merged);
                list.erase(j);
                is_merged = true;
                break;
            }
            else
            {
                ++j;
            }
        }
        if (is_merged)
        {
            i = list.erase(i); 
        }
        else
        {
            ++i;
        }
    }
    list.splice(list.end(), new_list);
}

这应该是有效的,因为插入和删除元素不会使任何其他元素的指针、引用和迭代器失效。〔参考:C++STL教程和参考,Nikolai Josuttis〕

您还没有提供太多见解,但这里有一个很好的方法:

#include <algorithm>
#include <set>
std::list<int> myList = {5,6,7,7,8,5};
std::set<int> uniques; // a set can only contain unique elements
// copying the list in the set will overwrite the same elements when duplicates are found
std::copy(myList.begin(), myList.end(), std::inserter(uniques, uniques.begin()));
// clear the existing list
myList.clear();
// copy back the unique elements.
std::copy(uniques.begin(), uniques.end(), std::back_inserter(myList));
for (int i: myList)
    cout << i << endl;

EDIT:就性能而言,它应该与您的解决方案相当或更快,因为我在集合中的搜索是使用0(log(N))算法完成的,而您有两个循环。

第二版:你需要一些比我想象的更复杂的东西。不过,一个开始的建议是:不能用迭代器循环,同时修改正在迭代的容器。您需要使用整数索引。

EDIT3:下面的解决方案提供了一种使用unordered_set的方法。你需要能够区分属于同一"组"的对象。

#include <unordered_set>
// That's the objects we are going to "fuse"
// In this example I suppose that  all Points that have the same X must be fused
struct Point
{
    double x;
    double y;
    // We need a way to say that which points are equals
    bool operator==(const Point& other) const
    {
        return other.x == x;
    }
};
// Then we need a unique identifier to place the points in a set
namespace std {
    template <> struct hash<Point> {
        double operator()(const Point& point) const {
            return point.x;
        }
    };
}
// We will use an unordered_set to put our elements in
unordered_set<Point> uniques;
// Then we can proceed as before
std::copy(myList.begin(), myList.end(), std::inserter(uniques, uniques.begin()));
myList.clear();
std::copy(uniques.begin(), uniques.end(), std::back_inserter(myList));

我认为修改(擦除)嵌套循环中的列表是个坏主意。外部循环迭代器(示例代码中的mylistterOutter)将无法正常工作。

你应该做两个单独的循环。第一个应该搜索要合并的项目,并以某种方式记住它们(不删除)。第二个循环将擦除记住的项目并创建新的项目。

以下是我最终得到的结果。

  1. 它是不规则的:配对不进行两次比较(0,1和1,0)

  2. 实例不与自身进行比较(0,0)

    #include <iostream>
    #include <list>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    class percepUnit {
        public:
            int cx, cy; // location of percept in frame
            bool remove; // used to delete percepts
            // constructor method
            percepUnit(int ix, int iy) {
                cx = ix;
                cy = iy;
                remove = false;
            }
    };
    bool canMerge(percepUnit unitA, percepUnit unitB) {
        double dist = sqrt(pow(abs(unitA.cx-unitB.cx),2)+ pow(abs(unitA.cy-unitB.cy),2));
        return (dist < 3);
    }
    percepUnit merge(percepUnit unitA, percepUnit unitB) {
        int x,y;
        x = unitA.cx+unitB.cx/2;
        y = unitA.cy+unitB.cy/2;
        return (percepUnit(x,y));
    }
    // Predicate to use remove_if to delete merge inputs.
    bool removePercepsMarkedForRemoval(const percepUnit &unit) {
        return unit.remove;
    }
    int main() {
        list<percepUnit> mylist;
        list<percepUnit> mergedlist;
        list<percepUnit>::iterator mylistiterOutter;
        list<percepUnit>::iterator mylistiterInner;
        mylist.push_back(percepUnit(0,0));
        mylist.push_back(percepUnit(2,2));
        mylist.push_back(percepUnit(5,5));
        mylist.push_back(percepUnit(7,7));
        mylist.push_back(percepUnit(15,15));
        for(mylistiterOutter = mylist.begin(); mylistiterOutter != mylist.end(); mylistiterOutter++) {
            mylistiterInner = mylistiterOutter; // bypass the same pair twice
            while (mylistiterInner != mylist.end()) {
                if (canMerge(*mylistiterOutter, *mylistiterInner) and mylistiterOutter != mylistiterInner) { // bypass the same instance
                    mergedlist.push_back(merge(*mylistiterOutter, *mylistiterInner));
                    mylistiterOutter->remove = true;
                    mylistiterInner->remove = true;
                }
                mylistiterInner++;
            }
        }
        mylist.erase(remove_if(mylist.begin(), mylist.end(), removePercepsMarkedForRemoval), mylist.end());
        mylist.splice(mylist.end(), mergedlist);
        return(0);
    }