【问题标题】:Best algorithm to group data by similarity (same ID)按相似度对数据进行分组的最佳算法(相同 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

标签: c++ algorithm vector grouping


【解决方案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);
      

      【讨论】:

        猜你喜欢
        • 2022-01-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-18
        • 2020-05-26
        • 1970-01-01
        • 2016-08-01
        相关资源
        最近更新 更多