问题 是否有一些STL函数可以获得两个C ++向量的笛卡尔积?


假设

b =  ["good ", "bad "]
a  = ["apple","mango"]
then output = ["good apple","good mango","bad apple","bad mango"]

我知道这可以用嵌套的for循环来完成,但是有一些优雅的衬里使用C ++ STL吗?


8737
2018-06-09 13:28


起源

可能有,但为什么让一切都更难理解?循环不是邪恶的。 - deviantfan
可能重复 从2个向量的连接构造一个向量 - Jonathan Mee
@JonathanMee:不是。这将创建长度为M * N的输出,从而创建长度为N + M的输出。 - MSalters
for (auto i : a) for (auto j : b) output.push_back(i + ' ' + j);  真? - Nim
@Jonathan Mee我不认为它是重复的,OP想要连接两个向量的笛卡尔积。 - Alessandro Teruzzi


答案:


这是一个单行(复制自 Jonathan Mee 回答 发布在这里):

for(size_t i = 0, s = a.size(); i < output.size(); ++i) output[i] = b[i/s] + ' ' + a[i%s];

完整的例子 这里


4
2018-06-09 15:00



这种模数技术真的很有启发性。谢谢 - Abhishek Kumar
@AbhishekKumar你会在中找到它的详细解释 我的答案......但是,正如我在评论中提到的那样,并非没有代价。模运算可能需要几个周期才能完成。 MSalter的回答 不会产生这样的费用,因此,我会将他作为最佳答案。 - Jonathan Mee
@JonathanMee:好的,所以:手动提升任何东西 没有任何特别的优势,嵌套循环获胜;但是,我的主要观点 - 当你用字符串做的时候, 它们难以区分。 - Matteo Italia
@JonathanMee:更有趣的是MSalter的版本是 一个数量级 在我的机器上更大的矢量更快。事实证明,使用64位机器上的代码,g ++能够在循环上执行自动矢量化,并发出一个 movdqa/paddd/movups 序列,实际上一次执行4个元素的求和。更好的是,用 -march=native 它使用AVX并直接进入 vpaddd/vmovup。 - Matteo Italia
@JonathanMee:嗯,这不是处理器“隐藏”任何东西,它更像是“分配一个新的 std::string 比那个循环中发生的任何事情都要昂贵得多,这种修改实际上只是噪音“。想想就像把一盒饼干放在汽车后备箱里 - 这不会影响你的里程数=)。 - Matteo Italia


没有直接的解决方案;我检查了整个 <algorithm>。这些函数都不产生长度为M * N的输出。

你是什​​么 能够 做就是打电话 std::for_each 在第一个范围,使用lambda调用 std::for_each 在第二个范围(!)

std::vector<std::string> a, b;
std::for_each(a.begin(), a.end(), 
  [&](std::string A) { std::for_each(b.begin(), b.end(),
      [A](std::string B) { std::cout << A << '/' << B << '\n';  }
);});

但这只是STL中的嵌套循环。


3
2018-06-09 13:48



我认为你必须成为一种特殊的受虐狂才能取代两个简单的基于范围的for循环并用此推回......;) - Nim
这是一个4线程(不包括声明)。 OP要求1-liner。 - Innocent Bystander
@InnocentBystander:我不知道他的显示器有多宽;) - MSalters
@MSalters,我知道他可以有一个 这些 :) - Innocent Bystander
@JonathanMee:这是为STL未能将迭代器范围实现为第一类对象而付出的代价。如果有一个概念 OutputRange,它可能有一个 .reserve 方法。 - MSalters


特定 vector<string> a 和 vector<string> b 您可以使用 for_each

vector<string> output(size(a) * size(b));

for_each(begin(output), end(output), [&, it = 0U](auto& i) mutable {
    i = a[it / size(b)] + ' ' + b[it % size(b)];
    ++it;
});

实例

编辑:

我们已经初始化了 output 有足够的空间来容纳每一个组合 a 和 b。然后我们将逐步完成每个元素 output 并分配它。

我们想要使用1ST 的元素 a 为了第一 size(b) 要点 output,和2ND 的元素 a 第二个 size(b) 元素,等等。所以我们通过索引来做到这一点 it / size(b)。我们希望通过迭代结合它 b的元素。

it 将移动到每个元素的下一个索引 output 但索引需要换行,否则它将超出界限 it == size(b),为此我们使用 it % size(b)

EDIT2:

这个问题 通过基准测试我发现了模数和除法是迭代的昂贵操作的现象。我在这里做了同样的测试。为了隔离算法,我只是在a上进行笛卡尔求和 vector<int> 不 vector<string>

首先,我们可以看到两种算法导致不同的装配。我上面写的算法需要585行汇编。我的解释需要588行 MSalter的代码

vector<string> output(size(testValues1) * size(testValues2));
auto i = begin(output);

std::for_each(cbegin(a), cend(a), [&](const auto& A) { std::for_each(cbegin(b), cend(b), [&](const auto& B) { *i++ = A + ' ' + B; }); });

我在这里做了一个非常可靠的基准测试: http://ideone.com/1YpzIO 在测试中我只设置了100次测试,但MSalters的算法总是获胜。本地使用Visual Studio 2015,发布时有10,000,000次测试,MSalters算法在我需要的时间内完成约2/3。

显然,模数不是一种很好的索引方法:(


3
2018-06-09 13:47



你介意详细说明吗? - abhishek_naik
这只写 cend(a)-cbegin(a) 要素 output。 - MSalters
@MSalters谢谢,我编辑。这是2次我误解了OP所要求的,2次你纠正了我。希望我们不必使它3 :( - Jonathan Mee
@BatCoder当然我已经更新了lambda正在做什么的解释。 - Jonathan Mee
实际上并不是一个惊喜 - 与整数加法相比,除法和模数相当慢。它正处于内存访问的关键路径上;每次读取主存都必须等待除法和模数完成。 - MSalters