[英]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. 从
StructA
和StructB
到StructC
没有转换,所以我们需要一个。 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
正如我之前在评论中所说,您可以为
A
和B
添加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 instancesa
inA
,b
inB
found to be equivalent, you want to mergea
,b
into a new objectc
and copy that to the output rangeC
.给定两个输入范围
A
,B
,每两个实例a
中A
,b
在B
发现是等价的,要合并a
,b
到一个新的对象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.