如何将std :: set_intersection用于2个不同但相关的类型并输出到另一个类型

时间:2014-04-18 16:25:48

标签: c++ c++11 stl stl-algorithm

我需要用std :: set_intersection做一些有点奇怪的事情,我无法弄明白。大约一个月前我问了一个类似的question,并且由于对这个问题的出色回答,我解决了使用两个向量之间的公共链接字段使std :: set_intersection工作的问题,每个向量包含不同的类型对象。

我现在面临的问题是我试图让下面的代码工作,我基本上需要将std :: set_intersection的输出写入一个新类型,它实际上是一些字段之间的联合StructA和StructB中的其他字段。我使用了由用户tclamb编写的略微修改的示例,但它没有编译,我在编译器错误中有点丢失。我很确定我面临的一些问题与

的限制有关

根据std::set_intersection中对类型的要求,InputIterator1和InputIterator2具有相同的值类型。在我的情况下,这不是真的,在tclamb解决方案的情况下也不是这样,但它似乎有效。

我刚刚编辑了下面的代码并整合了@ ivar对一些冗余代码的建议 - 这使得问题更容易阅读 - 它现在正在编译和运行 - 但仍然产生的结果不是我想要的。实时代码也发布在coliru

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

// I wish to return a vector of these as the result
struct StructC {
    std::string mCommonField;
    std::string mNameFromA; // cherry picked from StructA
    std::string mNameFromB; // cherry picked from StructB
    float mFloatFromA;      // cherry picked from StructA
    int mIntFromB;          // cherry picked from StructB
};

struct StructA {
    // conversion operator from StructA to StructC
    operator StructC() { return { mCommonField, mNameAString, "[]", mFloatValueA, 0 }; }
    std::string mCommonField;
    std::string mNameAString;
    float mFloatValueA;
};

struct StructB {
    // conversion operator from StructB to StructC
    operator StructC() { return { mCommonField, "[]", mNameBString, 0.0f, mIntValueB }; }
    std::string mCommonField;
    std::string mNameBString;
    int mIntValueB;
};

// Comparator thanks to @ivar
struct Comparator {
    template<typename A, typename B>
    bool operator()(const A& a, const B& b) const {
        return a.mCommonField < b.mCommonField;
    }
};

template<typename CharT, typename Traits>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, StructC const& sc) {
    return os << sc.mCommonField << " - " << sc.mNameFromA << " - " 
       << sc.mNameFromB << " - " << std::fixed << sc.mFloatFromA << " - " << sc.mIntFromB << std::endl;
}

int main() {
    Comparator comparator;

    // initially unsorted list of StructA
    std::vector<StructA> aStructs = {
        {"hello", "nameA1", 1.0f}, 
        {"goodbye", "nameA2", 2.0f}, 
        {"foo", "nameA3", 3.0f}
    };

    // initially unsorted list of StructB
    std::vector<StructB> bStructs = {
        {"hello", "nameB1", 10},     // <-- intersection as mCommonField("hello") also in aStructs
        {"goodbye", "nameB2", 20},   // <-- intersection as mCommonField("goodbye") also in aStructs
        {"bar", "nameB3", 30}
    };

    // in the above list, "hello" & "goodbye" are the common in both aStructs & bStructs

    // pre-sort both sets before calling std::intersection
    std::sort(aStructs.begin(), aStructs.end(), comparator);
    std::sort(bStructs.begin(), bStructs.end(), comparator);

    std::vector<StructC> intersection;
    std::set_intersection(aStructs.begin(), aStructs.end(),
                          bStructs.begin(), bStructs.end(),
                          std::back_inserter(intersection),
                          comparator);

    std::copy(intersection.begin(), intersection.end(),
              std::ostream_iterator<StructC>(std::cout, ""));
    return 0;
}

3 个答案:

答案 0 :(得分:3)

有两个错误。首先,back_inserter创建back_insert_iterator<vector<StructC>>,其operator=vector<StructC>::value_typeStructC。从StructAStructBStructC没有转换,所以我们需要一个。添加一个的最简单方法是

struct StructA {
    // ...
    operator StructC() { return {mCommonField, int(mFloatValue), 0}; }
};

等。其次,operator <<没有StructC。修复这些错误,我们有一个fully functional solution

UPD。您可以将结果放入vector<Common>,这看起来非常类似于此问题。

答案 1 :(得分:1)

我有点困惑但你的编辑,所以我希望我能得到最新的问题。

正如我之前在评论中所述,您可以为CA添加B个构造函数

struct StructA {
    std::string mCommonField;
    std::string mNameAString;
    float mFloatValueA;
};

struct StructB {
    std::string mCommonField;
    std::string mNameBString;
    int mIntValueB;
};

struct StructC {
    std::string mCommonField;
    std::string mNameFromA = "[]";
    std::string mNameFromB = "[]";
    float mFloatFromA = 0;
    int mIntFromB = 0;

    StructC(const StructA& a) : mCommonField{a.mCommonField},
        mNameFromA{a.mNameAString}, mFloatFromA{a.mFloatValueA} {}

    StructC(const StructB& b) : mCommonField{b.mCommonField},
        mNameFromB{b.mNameBString}, mIntFromB{b.mIntValueB} {}
};

而非A / B次转化为C。我们的想法是C应该更好地了解如何从部分信息中构建自己。我认为这是您在polkovnikov.ph's answer下面的评论中所关注的问题。此外,使用类内初始化程序,可以将成员初始化为默认值,以便每个构造函数只关心相关内容。

另一个问题是,您并不需要构建Common个对象来进行比较;这是纯粹的开销。您可以使用通用比较器执行相同的操作:

struct Comparator
{
    template<typename A, typename B>
    bool operator()(const A& a, const B& b) const
    {
        return a.mCommonField < b.mCommonField;
    }
};

现在不再需要struct Common(除非你出于其他目的需要它)。

这里是complete live example

除此之外,我没有看到任何问题。

答案 2 :(得分:1)

我想我现在更清楚你最初在寻找什么:

  

给定两个输入范围AB,对于a中的A bB中找到a为了等效,您希望将bc合并到新对象C中,并将其复制到输出范围std::set_intersection

如果我现在这样做,恐怕我无法想到可以实现这一目标的标准算法的组合。 std::set_intersection根本不是 泛型。

所以我的第二次尝试是通过添加Merger函数对象来概括template< class InputIt1, class InputIt2, class OutputIt, class Compare, class Merge > OutputIt set_intersection ( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first, Compare comp, Merge merge ) { while (first1 != last1 && first2 != last2) { if (comp(*first1, *first2)) ++first1; else { if (!comp(*first2, *first1)) *d_first++ = merge(*first1++, *first2); ++first2; } } return d_first; }

Merger

这是struct Merger { template<typename A, typename B> StructC operator()(const A& a, const B& b) const { return {a, b}; } }; 课程的样子:

StructC

以及struct StructC { std::string mCommonField; std::string mNameFromA; std::string mNameFromB; float mFloatFromA; int mIntFromB; StructC(const StructA& a, const StructB& b) : mCommonField{a.mCommonField}, mNameFromA{a.mNameAString}, mNameFromB{b.mNameBString}, mFloatFromA{a.mFloatValueA}, mIntFromB{b.mIntValueB} {} }; 实际处理合并的方式:

goodbye - nameA2 - nameB2 - 2.000000 - 20
hello - nameA1 - nameB1 - 1.000000 - 10

根据需要,live example现在提供

std::set_intesection

注意:{{1}}的代码就是我从cppreference.com复制的代码,这是一个简化的功能等价物。在生产代码中,我会检查与转发,异常,特殊情况,优化以及我没有想到的任何问题相关的实际实现。