简体   繁体   English

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

[英]How can I use std::set_intersection for 2 distinct but related types and output into another type

I need to do something a little strange with std::set_intersection and I cannot quite figure it out. 我需要对std :: set_intersection做一些奇怪的事情,但我不太清楚。 I asked a similar question about a month ago, and thanks to the excellent responses to the question, I solved the issue of making the std::set_intersection work using a common link field between 2 vectors, each containing a different type of object. 大约一个月前,我问了一个类似的问题 ,并且由于对此问题的出色回答,我使用两个向量之间的公共链接字段解决了使std :: set_intersection工作的问题,每个向量都包含不同类型的对象。

The problem that I am now facing is that I am trying to get the code below to work, I basically need to write std::set_intersection's output to a new type which is effectively a union between some fields from StructA and other fields from StructB. 我现在面临的问题是我试图使下面的代码正常工作,我基本上需要将std :: set_intersection的输出编写为新类型,这实际上是StructA的某些字段与StructB的其他字段之间的联合。 I used a slightly modified sample written by user tclamb but it doesn't compile and I am a bit lost in the compiler errors. 我使用了一个由用户tclamb编写的经过稍微修改的示例,但是它无法编译,并且我对编译器错误有些迷失。 I am pretty sure that some of the problems I am facing are to do with the restriction that 我很确定我面临的一些问题与限制有关

According to the section Requirements on types in std::set_intersection InputIterator1 and InputIterator2 have the same value type. 根据“ std :: set_intersection中的类型要求”部分,InputIterator1和InputIterator2具有相同的值类型。 In my case this is not true, also in the case of the solution by tclamb it was also not the case, however it seemed to work. 在我的情况下,这是不正确的,在tclamb解决方案的情况下,情况也不是,但是似乎可行。

I just edited the code below and incorporated @ivar's suggestions for some redundant code - this makes the problem easier to read - it is now compiling and running - but still producing results that are not quite what I want. 我只是编辑了下面的代码,并结合了@ivar对于一些冗余代码的建议-这使问题更易于阅读-现在正在编译和运行-但仍然产生的结果不是我想要的。 The live code is also posted at coliru 实时代码也发布在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;
}

There're two mistakes. 有两个错误。 First, back_inserter creates a back_insert_iterator<vector<StructC>> , which has operator= on vector<StructC>::value_type , which is StructC . 首先, back_inserter创建一个back_insert_iterator<vector<StructC>> ,它在vector<StructC>::value_type上具有operator= ,即StructC There's no conversion from StructA and StructB to StructC , so we need one. StructAStructBStructC没有转换,所以我们需要一个。 The easiest way to add one is 添加一个的最简单方法是

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

etc. Second, there's no operator << for StructC . 第二,对于StructC没有operator << Fixing these bugs we have a fully functional solution . 修复这些错误,我们有一个功能齐全的解决方案

UPD. UPD。 Either you could put your results into vector<Common> , which looks very much like it's designed for this issue. 您可以将结果放入vector<Common> ,它看起来非常像是为该问题设计的。

I am a bit confused but your edits, so I hope I get the latest question right. 我有点困惑,但是您的修改,所以希望我能正确回答最新的问题。

As I said previously in a comment, you can add to C constructors for A and B 正如我之前在评论中所说,您可以为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} {}
};

instead of A / B conversions to C . 而不是将A / B转换为C The idea is that C should know better how to construct itself from partial information. 这个想法是C应该更好地了解如何从部分信息中构造自己。 I think this was your concern in the comment below polkovnikov.ph's answer . 我认为这是您在polkovnikov.ph的回答下面的评论中所关注的 Plus, with in-class initializers, members can be initialized to default values so that each constructor only cares for what is relevant. 另外,使用类内初始化程序,可以将成员初始化为默认值,以便每个构造函数仅关心相关内容。

Another issue is that you don't really need to construct Common objects just for comparison; 另一个问题是,您不必真正为了比较而构造Common对象。 it's pure overhead. 这纯粹是开销。 You can do the same with a generic comparator: 您可以使用通用比较器执行相同操作:

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

Now struct Common is not needed anymore (except if you need it for other purposes). 现在不再需要struct Common (除非您出于其他目的需要它)。

Here's a complete live example . 这是一个完整的实时示例

Other than that, I don't see any problem. 除此之外,我看不到任何问题。

I think I now have a better idea what you were originally looking for: 我想我现在有了一个更好的主意,您最初想要的是什么:

Given two input ranges A , B , for every two instances a in A , b in B found to be equivalent, you want to merge a , b into a new object c and copy that to the output range C . 给定两个输入范围AB ,每两个实例aAbB发现是等价的,要合并ab到一个新的对象c并复制到输出范围C

If I got this right now, I am afraid I can think of no combination of standard algorithms that can accomplish that. 如果我现在就知道这一点,恐怕我想不出能实现此目标的标准算法的组合。 std::set_intersection is simply not that generic. std::set_intersection根本不是那么通用。

So my second attempt is to generalize std::set_intersection itself by adding a Merger function object: 所以我的第二次尝试是通过添加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;
}

Here's what your Merger class can look like: 这是您的Merger类的样子:

struct Merger
{
    template<typename A, typename B>
    StructC operator()(const A& a, const B& b) const { return {a, b}; }
};

and here's how StructC actually handles merging: 这是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} {}
};

As you require, the live example now gives 根据您的要求,现在的示例

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

Watch out: the code of std::set_intesection is just what I copied from cppreference.com , which is a simplified functional equivalent. 请注意: std::set_intesection的代码正是我从cppreference.com复制的代码,它是等效的简化功能。 In production code, I would check actual implementations for issues related to forwarding, exceptions, special cases, optimizations and anything I haven't thought of. 在生产代码中,我将检查实际实现中是否存在与转发,异常,特殊情况,优化以及我未曾想到的任何事情有关的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM