【问题标题】:How can I use std::set_intersection for 2 distinct but related types and output into another type如何将 std::set_intersection 用于 2 个不同但相关的类型并输出到另一种类型
【发布时间】:2014-04-18 16:25:47
【问题描述】:

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

我现在面临的问题是我试图让下面的代码工作,我基本上需要将 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;
}

【问题讨论】:

  • AB 作为输入参数添加到C 两个复制/移动构造函数。我认为这应该足够了。
  • 帮自己一个忙,不要再看 SGI STL 文档了:C++ 标准库实际上是在 20 年前从 STL 派生出来的,从那时起事情就发生了分歧。例如,cppreference.com page for std::set_intersection 没有列出两种输入迭代器类型需要具有相同值类型的要求——因为 C++ 标准不要求它们这样做。
  • 谢谢,这是个好建议,它表明“类型 Type1 和 Type2 必须使得 InputIt1 和 InputIt2 类型的对象可以被取消引用,然后分别隐式转换为 Type1 和 Type2。”
  • @iavr 你能给我一个简单的例子来说明你的意思吗?我将我的代码更改为 StructA 和 StructB 的 C 转换运算符 - 这不是等效的,即使如此它仍然不足以满足我的需求。如果 StructA 和 StructB 不是我的结构并因此不可修改,我会认为这是一件有用的事情。
  • @johnco3 看我的回答。

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


【解决方案1】:

有两个错误。首先,back_inserter 创建一个back_insert_iterator&lt;vector&lt;StructC&gt;&gt;,它在vector&lt;StructC&gt;::value_type 上有operator=,即StructC。没有从StructAStructBStructC 的转换,所以我们需要一个。添加一个最简单的方法是

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

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

UPD。或者你可以把你的结果放到vector&lt;Common&gt;,看起来很像它是为这个问题设计的。

【讨论】:

  • 谢谢,这真的很有帮助,但是现在您帮助我正确转换为输出迭代器 - 通过运算符转换语义,问题有点复杂。请参阅coliru.stacked-crooked.com/a/9c1bd60ffdf37a56,在那里我更新了 cmets 和代码以了解我的意思,基本上,除了 StructA 和 StructB 中的常见字段之外,我还需要字段在 StructC 中 - 但是 StructA 中的转换运算符需要 StructB 中的字段才能真正填充StructC 正确,我不确定这是否可能。不过很有帮助的答案。
  • 我也不确定为什么从 StructA 到 StructC 而不是从 StructB 到 StructC 只需要 1 个转换运算符。我怀疑 set::intersection 在找到匹配项后的实现 - 从 InputIterator1 插入匹配的副本 - 有效地 *outputIter = *matchingIterator1 - 但是我需要 *matchingIterator1 和 *matchingIterator2 (StructA 和 StructB)之间的一种合并但我不知道如何实现。
  • @johnco3 如果您仔细阅读文档,它会说“如果某个元素在 [first1, last1) 中找到 m 次,在 [first2, last2) 中找到 n 次,则第一个 std::min( m, n) 元素将从第一个范围复制到目标范围。"这回答了您为什么只需要从A 进行一次转换的问题。
  • @johnco3 至于你所暗示的那种合并,恐怕std::intersection 不是那个通用的。它有一个用于比较的函数对象,但它需要另一个函数对象,它接受两个参数 A,B 用于找到的两个等效项,并给出一个 C 以复制到输出。你可以让这个对象进行合并。目前只复制A。我想我只是把std::intersection的代码自己概括一下。
【解决方案2】:

我想我现在对您最初寻找的内容有了更好的了解:

给定两个输入范围 AB,对于每两个实例 a in Ab in B 发现是等价的,你想合并 ab 到一个新对象 c 并将其复制到输出范围 C

如果我现在得到这个,恐怕我想不出任何标准算法的组合可以做到这一点。 std::set_intersection 根本不是那个通用的。

所以我的第二次尝试是通过添加Merger 函数对象来概括std::set_intersection 本身:

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} {}
};

根据您的需要,live example 现在提供

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

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

【讨论】:

  • 太棒了,非常感谢这个通用解决方案,它可以为我节省大量 std::find_if
【解决方案3】:

我有点困惑,但你的编辑,所以我希望我能正确回答最新的问题。

正如我之前在评论中所说,您可以为AB 添加C 构造函数

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

除此之外,我没有发现任何问题。

【讨论】:

  • 感谢对实时示例的更新,但是实时示例中的结果并不是我想要的。当前的实时示例给出了以下内容(从 B 的字段中输入默认构造函数值)再见 - nameA2 - [] - 2.000000 - 0 hello - nameA1 - [] - 1.000000 - 0 我希望在以下几行中有一些东西B 的值与 A 的值合并 再见 - nameA2 - nameB2 - 2.000000 - 20 hello - nameA1 - nameB1 - 1.000000 - 10
  • 也许只是不清楚您在寻找什么。我想我现在从你的 cmets 那里有一个更好的主意,我会写另一个答案。
  • 非常感谢,我不确定它是否可能,祝你好运:)
  • @johnco3 完成,再次检查。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-18
  • 2021-04-11
  • 2011-05-30
  • 2022-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多