我有2个字符串向量(一个大约是另一个的1/3)。我试图实现一个算法,将两个向量随机混合在一起。在结果向量中,先前在向量A中的项目可以相互跟随,但是向量B中的项目不能。
例如,如果向量A中的每个元素都是“FOO”并且向量B中的每个元素都是“BAR”,那么得到的向量可能是{“FOO”,“FOO”,“BAR”,“FOO”,“ BAR”, “FOO”, “FOO”, “BAR”}
正如您所看到的,“FOO”可能会重复,但“BAR”不能
这大致是我到目前为止:
#include <string>
#include <chrono>
#include <algorithm>
#include <random>
#include <vector>
std::vector<std::string> a(1000, "FOO");
std::vector<std::string> b(300, "BAR");
std::vector<std::string> result;
bool resultIsValid();
void mergeVectors()
{
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
std::mt19937 generator(seed);
result = a;
result.insert(result.end(), b.begin(), b.end());
while (!resultIsValid())
{
std::shuffle(a.begin(), a.end(), generator);
}
}
bool resultIsValid()
{
for(int i=0; i<result.size()-2; ++i)
if (result[i] == "BAR" && result[i+1] == "BAR")
return false;
return true;
}
这不是实际的代码,但这应该给出一个想法。当我运行它时,程序进入无限循环,因为字符串的实际数量要高得多(在10000范围内),它永远不会得到有效的向量。总是存在至少一个“BAR”顺序重复。有人能够建议一个更好的选择,然后只是继续重新检查创建的矢量为重复的“BAR”?我是否比这更复杂?
答案 0 :(得分:6)
结果列表包含"BAR","FOO"
和"FOO"
个元素。例如
{"FOO","FOO","BAR","FOO","BAR","FOO","FOO","BAR","FOO"}
可以拆分为
"FOO" | "FOO" | "BAR","FOO" | "BAR","FOO" | "FOO" | "BAR","FOO"
可以压缩为
{0, 0, 1, 1, 0, 1}
其中0表示单个元素,1表示从"BAR"
到"FOO"
的转换。
0
和1
的数量是不变的,因此可以生成包含这些内容的向量并将其随机播放。
唯一的问题是在最后,单个"BAR"
也是有效的(如果你将"BAR","FOO"
视为原始元素,那么在开始时会出现同样的问题。)
如果包含"FOO"
的向量增加1个虚拟元素(sentinel),则可以解决此问题。结果列表始终以"FOO"
元素结尾,但在其他方面确实是随机的。但是我们可以安全地删除最后一个元素,因为这是我们的虚拟元素。
实现算法的简单代码(没有在Iterators和Allocators上进行模板化)可能如下所示:
std::vector<std::string> mergeVectors(std::vector<std::string> const& canvas,
std::vector<std::string> const& sprinkle)
{
assert (canvas.size() + 1>= sprinkle.size()); // otherwise impossible
std::vector<int> transitions; // 1 for [sprinkle, canvas]
// 0 for single [canvas]
// sprinkle.size() times [canvas, sprinkle]
transitions.insert(transitions.end(), sprinkle.size(), 1);
// rest is [canvas].
transitions.insert(transitions.end(), canvas.size() - sprinkle.size() + 1, 0);
// There is a problem with the last element since this always is from canvas
// as well. So we set the last canvas to a sentinel element which is always removed.
// This way, we can ensure that the result is truly randomly distributed.
std::mt19937 generator(std::chrono::system_clock::now().time_since_epoch().count());
std::shuffle(transitions.begin(), transitions.end(), generator);
bool last_is_sprinkle = transitions.back(); transitions.pop_back();
std::vector<std::string> result;
auto canvas_it = canvas.begin();
auto sprinkle_it = sprinkle.begin();
for (auto t : transitions) {
if (t) result.push_back(*sprinkle_it++);
result.push_back(*canvas_it++);
}
if (last_is_sprinkle)
result.push_back(*sprinkle_it);
return result;
}
答案 1 :(得分:2)
我们可以根据剩余要从A和B中分配的元素数量计算结果向量中下一个元素应该来自A或B的概率,并从A或B中随机选择下一个元素就那个概率而言。
如果选择的最后一个元素来自列表B(这会阻止来自B的连续元素),从列表A中选择下一个元素的概率总是100%。
如果A中剩余的元素数等于B中剩余的元素数,那么如果添加到结果列表中的最后一个元素来自A,那么从B中获取下一个元素的概率是100%。 / p>
否则,从A中选择元素的概率应该等于(A.length() - B.length())/A.length()
,假设我们将元素从A和B中取出,因为我们将它们放在结果向量中,所以它们的长度在整个过程中减少到零。我们可以通过测试0到1之间随机生成的值来确定该元素是否应来自列表A或列表B.
这应该保证As和Bs被均匀地洗牌,可以通过多次运行程序并比较结果向量的每一半中的B数来测试。
(编辑)
我认为在Python中测试这个算法更快,所以这是我的Python实现:
from random import random
def intersperse(a,b):
la = len(a)
lb = len(b)
ia = 0
ib = 0
bWasLast = False
res = []
while (ia < la) or (ib < lb):
if bWasLast:
res.append(a[ia])
ia += 1
bWasLast = False
elif ((lb - ib) > (la - ia)) and not bWasLast:
res.append(b[ib])
ib += 1
bWasLast = True
else:
laRemaining = la - ia
lbRemaining = lb - ib
probA = (laRemaining - lbRemaining)/laRemaining
if random() < probA:
res.append(a[ia])
ia += 1
bWasLast = False
else:
res.append(b[ib])
ib += 1
bWasLast = True
return res
测试代码如下:
A = 'a'*10000
B = 'b'*3000
sumBLeft = 0
sumBRight = 0
for n in range(100):
r = intersperse(A,B)
sumBLeft += sum([1 for x in r[:len(r)//2] if x == 'b'])
sumBRight += sum([1 for x in r[len(r)//2:] if x == 'b'])
print (sumBLeft/sumBRight)
该程序的输出结果显示,结果左侧的数量仅比右侧的数量大0.08%。我的测试结果的一面,确认通过结果向量的分布是均匀的。
答案 2 :(得分:0)
就我从你的代码中看到的那样,你得到了好运,直到你得到的混合是有效的。我的方法如下:
foo
带有“FOO”项目的向量和bar
带有“BAR”项目的向量,并且F
和B
各自的大小。<登记/>
除非“BAR”项是结果序列中的第一项,否则任何条形项必须以“FOO”项开头。因此,如果我们将“BAR”及其前面的“FOO”组合在一起,我们会得到B
“FOOBAR”序列(或B-1
,如果我们以“BAR”开头)和{{1 (或F-B
)“FOO”介于两者之间。
结果序列以“BAR”项开头的概率为F-(B-1)
。
我首先为“BAR”和“FOO”项目的混合制作一个模式向量,然后将实际结果向量混合在一起。该模式将由“FOOBAR”和“FOO”项组成,因为“BAR”项必须以“FOO”开头,除非结果序列以“BAR”开头
伪代码:
B/(F+1)
对于示例bool startsWithBar = B < (random * (F+1));
//if the sequence starts with a BAR, there are B-1 FOOBAR items
int nFOOBAR = B - (startsWithBar ? 1 : 0);
int nFOO = F - nFOOBAR;
vector<char> pattern(nFOOBAR, 'm'); //m for mix - FOOBAR = FOO followed by a BAR
pattern.insert(pattern.end(), nFOO, 'f');
shuffle(pattern);
shuffle(foo);
shuffle(bar);
vector<string> result;
result.reserve(F+B);
auto itBar = begin(bar);
auto itFoo = begin(foo);
//fill the result according to the pattern
if (startsWithBar)
result.push_back(*itBar++);
for (char patt : pattern)
{
switch (patt)
{
case 'f': //"FOO"
result.push_back(*itFoo++);
break;
case 'm': //"FOOBAR"
result.push_back(*itFoo++);
result.push_back(*itBar++);
break;
}
}
,模式为{"FOO","FOO","BAR","FOO","BAR","FOO","FOO","BAR"}
答案 3 :(得分:0)
如果您的第二个向量v2
比v1
短,则必须将v2
的每个元素复制到输出向量vout
中,并追加v1
的一个或多个元素1}}每一次。一种解决方案是,枚举v1
的所有可能索引并随机擦除它们,直到只剩下多个索引,因为元素在v2
中。因此,两个相邻索引之间的差异是v1
中元素的数量,您在vout
元素之后插入v2
。例如:
using size_type = std::vector<std::string>::size_type;
std::vector<size_type> vid(v1.size());
std::iota(begin(vid), end(vid), 0);
std::random_shuffle(begin(vid), end(vid));
vid.erase(std::next(begin(vid), v2.size()), end(vid));
std::sort(begin(vid), end(vid));
size_type id_last = 0;
for(size_type i = 0; i < vid.size(); ++i) {
vout.insert(end(vout), std::next(begin(v1), id_last),
std::next(begin(v1), vid[i]));
vout.push_back(v2[i]);
id_last = vid[i];
}
vout.insert(end(vout), std::next(begin(v1), vid.back()), end(v1));
这可能不是最快的方法,但它应该勾勒出背后的想法。我相信整个索引管理也可以使用boost中的一些迭代器适配器来重写。此外,如果合并后不需要原始字符串向量,则可以移动字符串而不是复制它们。
答案 4 :(得分:0)
一个想法是如下:
|B|
,您知道必须在它们之间放置至少|B|-1
个元素。|A|-(|B|-1)
插入操作。假设你有:
A = {FOO1, FOO2, FOO3, FOO4, FOO5}
B = {BAR1, BAR2, BAR3}
第1步&amp; 2:
A = {FOO4, FOO3, FOO1, FOO5, FOO2}
C = {BAR3, BAR1, BAR2}
第3步:
A = {FOO1, FOO5, FOO2}
C = {BAR3, FOO4, BAR1, FOO3, BAR2}
第4步:
C = {FOO1, FOO5, BAR3, FOO4, BAR1, FOO3, FOO2, BAR2}