按相似度对数据进行分组的最佳算法(相同 ID)

Posted

技术标签:

【中文标题】按相似度对数据进行分组的最佳算法(相同 ID)【英文标题】:Best algorithm to group data by similarity (same ID) 【发布时间】:2015-08-05 13:42:12 【问题描述】:

这里是参考代码:

struct MyData

int ID;

// other members
;

std::vector<MyData> inputData;

std::vector<std::vector<MyData> > outputData = GroupByIDs(inputData);

基本上我想要做的是迭代输入数据并按 ID 将对象分组到一个新的迷你向量中,我将把它推入输出向量。所以最后我会有一个子向量的向量,其中每个子向量都包含具有相同 ID 的对象。

是否有一种 cookie-cutter 最有效的算法可以做到这一点?因为我只能想到复杂度很高的算法。

【问题讨论】:

您有什么顾虑?如何确定组,如何确定相似度?或者如何有效地将数据从 inputData 传输到 outputData? 目前我保留了一个缓冲区来保存已经添加的 ID,并且每次迭代都会进行大量搜索,我需要一种有效的方法来完成它 好的,相似度是如何定义的?利用相似性定义的属性具有很大的优化潜力。 @peterchen 我编辑了这个问题,通过相似性我的意思是相同的 ID 【参考方案1】:

您可以通过根据ID 对元素进行排序,然后使用std::upper_bound 查找每个组的结束位置来做到这一点:

例如:

#include <string>
#include <vector>
#include <iostream>
#include <algorithm>

struct MyData

    int id;
    std::string info;

    MyData(int id, const std::string& info): id(id), info(info) 

    // for sorting by id
    bool operator<(const MyData& d) const  return id < d.id; 
;

// function requires sorted data as input
std::vector<std::vector<MyData> > GroupByIDs(const std::vector<MyData>& data)

    std::vector<std::vector<MyData> > groups;

    decltype(data.end()) upper;

    for(auto lower = data.begin(); lower != data.end(); lower = upper)
    
        // get the upper position of all elements with the same ID
        upper = std::upper_bound(data.begin(), data.end(), *lower);

        // add those elements as a group to the output vector
        groups.emplace_back(lower, upper);
    

    return groups;


int main()

    std::vector<MyData> data 2, "A", 4, "B", 3, "C", 4, "D", 9, "E", 3, "F";

    // function requires sorted data
    std::sort(data.begin(), data.end());
    std::vector<std::vector<MyData> > groups = GroupByIDs(data);

    for(auto const& group: groups)
    
        if(!group.empty())
            std::cout << "group: " << group.front().id << '\n';

        for(auto const& d: group)
            std::cout << "     : " << d.info << '\n';

        std::cout << '\n';
    

输出:

group: 2
     : A

group: 3
     : C
     : F

group: 4
     : B
     : D

group: 9
     : E

【讨论】:

【参考方案2】:

我编辑了问题,通过相似性我的意思是相同的 ID

实施:

auto MapByIDs(std::vector<MyData> inputData)

    std::map<std::vector<MyData>> result;
    for(auto &x: inputData)
        result[x.ID].emplace_back(std::move(x));
    return result;


auto GroupByIDs(std::vector<MyData> inputData)

    auto map = MapByIDs(std::move(inputData));
    std::vector<std::vector<MyData>> result;
    for(auto &x: map)
        result.emplace_back(std::move(x.second));
    return result;


auto outputData = GroupByIDs(std::move(inputData));

【讨论】:

【参考方案3】:

您有点含糊,但我想您可以使用std::sort 或朋友(std::stable_sortstd::partitionstd::stable_partition)。然后你使用std::copy 从一个向量的迭代器到另一个向量。

【讨论】:

是的,这可能行得通,只需对它们进行排序,然后它们将每个“相同 ID”段复制到一个新向量中。【参考方案4】:

如果您使用哈希表,您可以执行以下操作:

Create a hash table that maps from ID to vector<MyData>
Iterate through the input data:
    If the hash table doesn't contain a vector for that ID:
        Create a vector<MyData> and add it to the hash table
    Push the input item into that vector<MyData>
Iterate through the entry set for the hash table:
    Put the vector<MyData> into the vector<vector<MyData>>
Return the vector<vector<MyData>>

这应该类似于O(n) 平均情况。最坏的情况是O(n^2),我想,如果散列函数不好的话。

【讨论】:

请注意,point3&4 是使用 std::unordered_map&lt;int, std:::vector&lt;MyData&gt;&gt;::operator[] 自动完成的 :-) 所以只需要 map[data.ID].push_back(data) @Jarod42 我不是 C++ 程序员。感谢您的观察。【参考方案5】:

您可以保存 ID 的映射,该映射在 ID 和包含具有 ID 的项目的向量之间进行映射。然后遍历输入,为每个元素检查地图的 ID,如果是新元素,则创建一个新向量。您的复杂度将是 O(NlogM),其中 N 是输入大小和 M 个可能的 ID。

伪代码:

    for(Item in inputData)
            if(Item.ID in IDMap)
                    IDVec = IDMap[Item.ID]
                    IDVec.push(Item)
            else
                    IDVec = new Vector
                    IDMap.push(IDVec, Item.ID)
                    OutputVec.push(IDVec)

【讨论】:

if(Item.ID in IDMap) 不需要映射:IDMap[Item.ID].push_back(Item); 就足够了。 确实如此,我只是写的更简单,以便清楚复杂性【参考方案6】:

选项 1: 按 ID 对输入进行排序,然后对排序后的输入进行迭代,累积和复制具有相同 ID 的数据序列,并使用向量的vector(iter, iter)CTor 将它们复制到目标。

排序需要一个比较器:

bool less_than_by_ID(MyData const & a, MyData const & b) 
 return a.ID < b.ID; 

选项 2: 将项目保存在以 ID 为键的 multiset 或 multimap 中,使用各自容器的 lower_boundupper_bound 获取各个键的范围。 (算法上是一样的)

选项 3: 将输出数据结构更改为

std::map<int, std::vector<MyData> > outputData;

然后将数据扔到容器中:

for(auto data : inputData)
  outuptData[data.ID].push_back(data);

【讨论】:

以上是关于按相似度对数据进行分组的最佳算法(相同 ID)的主要内容,如果未能解决你的问题,请参考以下文章

使用 Jaccard 相似度对分类数据进行聚类

使用余弦相似度对文档进行分类

有没有一种根据 Jaccard 相似度对图进行聚类的有效方法?

NLPPython实例:基于文本相似度对申报项目进行查重设计

对相似参数进行分组的逻辑

R语言计算杰卡德相似系数(Jaccard Similarity)实战:自定义函数计算Jaccard相似度对字符串向量计算Jaccard相似度将Jaccard相似度转化为Jaccard距离