我试图接受两个bitset
个对象,例如
a = 10010111
b = 01110010
并删除两个变量中的位,如果它们在相同的位置/索引中匹配。所以我们留下了
a = 100xx1x1 = 10011
b = 011xx0x0 = 01100
有没有办法实现这个目标?
答案 0 :(得分:3)
其他答案显示了很好的,惯用的C ++方法。不幸的是,它们会变得相当缓慢。即使AndyG's clever template-based solution,尽管它在编译时尽可能多地完成了工作,但仍会导致编译器生成大量必须在运行时执行的代码。
如果您关心速度并且定位的是支持BMI2 instruction set的处理器(可能是Intel Haswell及更高版本,或AMD挖掘机及更高版本),那么您可以使用执行的PEXT
instruction a parallel bit extraction。这使您可以在大约两个机器指令中解决整个问题。
由于您没有在汇编语言中编写,因此您将使用PEXT
指令的相应内在函数,即_pext_u32
。在其基本形式中,代码简单,可读,并且非常高效:
#include <stdint.h> // for uint32_t
#include <x86intrin.h> // for _pext_u32() [on MSVC, drop the 'x86']
void RemoveMatchingBits(uint32_t& a, uint32_t& b)
{
const uint32_t mask = (a ^ b);
a = _pext_u32(a, mask);
b = _pext_u32(b, mask);
}
首先,您将两个值(a
和b
一起按位XOR)。这将生成一个掩码,如果相应的位在 a
或 b
中设置,则掩码中的每个位都会置位,否则位未设置。然后将该掩码用作_pext_u32
执行的位提取的基础。两个位提取操作都使用相同的掩码,因此只需要一条XOR
指令。每个_pext_u32
内在函数将编译为PEXT
指令。因此,除了一些MOV
指令来重新排列值(这取决于用于生成代码的编译器以及此代码是否内联),只需要三个机器代码指令。以下是GCC和Clang的当代版本如何编译上述函数(MSVC和ICC发出非常相似的代码):
RemoveMatchingBits(unsigned int&, unsigned int&):
mov eax, DWORD PTR [rdi] // rdi contains a pointer to 'a'
mov edx, DWORD PTR [rsi] // rsi contains a pointer to 'b'
xor edx, eax
pext eax, eax, edx
mov DWORD PTR [rdi], eax
mov eax, DWORD PTR [rsi]
pext eax, eax, edx
mov DWORD PTR [rsi], eax
ret
正如您所看到的,这里的大多数额外指令都是MOV
s,由我们编写函数以通过引用接受其参数并在适当位置修改这些值的方式强制执行。调整函数的编写方式,和/或让优化器在调用站点内联函数,将会产生更高效的实现。
如果您想使用std::bitset
,只需稍微修改一下代码即可。 to_ulong()
成员函数允许您访问原始位以进行操作。类似的东西:
void RemoveMatchingBits(std::bitset<8>& a, std::bitset<8>& b)
{
const std::bitset<8> mask = (a ^ b);
a = _pext_u32(static_cast<uint32_t>(a.to_ulong()), static_cast<uint32_t>(mask.to_ulong()));
b = _pext_u32(static_cast<uint32_t>(b.to_ulong()), static_cast<uint32_t>(mask.to_ulong()));
}
请注意,考虑到需要处理std::bitset
对象,这会进一步降低生成代码的效率。特别是,to_ulong()
成员函数必须在溢出的情况下检测并抛出异常,并且MSVC似乎无法优化该检出,即使std::bitset<8>
不可能溢出32位整数类型。好吧 - 代码将足够快,没有人说抽象是完全免费的。
如果您无法编译假设BMI2支持,您可以在运行时使用CPUID
instruction进行检查(几乎所有x86编译器都为此提供内在函数。)
如果它不可用,那么您不是以x86为目标,或者如果您只是不想担心运行时委派的复杂性,那么您可以回到另一个有点笨拙的实现。具体来说,你想要的是“压缩”操作。关于这方面的讨论和代码在Henry S. Warren,Jr。的经典着作Hacker's Delight的第7-4节中给出。
这是一个简单的,基于循环的“压缩”实现,改编自 Hacker's Delight 中的图7-9:
uint32_t compress(uint32_t value, uint32_t mask)
{
uint32_t result = 0;
uint32_t shift = 0;
uint32_t maskBit;
do
{
maskBit = (mask & 1);
result |= ((value & maskBit) << shift);
shift += maskBit;
value >>= 1;
mask >>= 1;
} while (mask != 0);
return result;
}
这充分模拟了PEXT
指令,但速度并不快。以下代码实现了相同的算法,但在 Hacker's Delight 中使用了基于图7-10的更快的“并行后缀”方法:
uint32_t fallback_pext_u32(uint32_t value, uint32_t mask)
{
const int log2BitSize = 5; // log_2 of the bit size (here, 32 bits)
uint32_t mk = (~mask << 1); // we will count 0's to the right
uint32_t mp;
uint32_t mv;
uint32_t t;
value = (value & mask); // clear irrelevant bits
for (int i = 0; i < log2BitSize; ++i)
{
mp = mk ^ (mk << 1); // parallel suffix
mp = mp ^ (mp << 2);
mp = mp ^ (mp << 4);
mp = mp ^ (mp << 8);
mp = mp ^ (mp << 16);
mv = (mp & mask); // bits to move
mask = ((mask ^ mv) | (mv >> (1 << i))); // compress mask
t = (value & mv);
value = ((value ^ t) | (t >> (1 << i))); // compress value
mk &= ~mp;
}
return value;
}
此回退实现比单个PEXT
指令慢,但它完全没有分支,因此在处理随机输入时,错误预测的分支不会有任何隐藏的惩罚。您应该从CPU获得最大可能的吞吐量,但无论哪种方式,它肯定比具有一系列条件分支的for
循环快得多,如其他答案所建议的那样。
答案 1 :(得分:2)
您可以使用boost::dynamic_bitset<>
作为结果,然后使用push_back
动态创建位集。
#include <iostream>
#include <boost/dynamic_bitset.hpp>
#include <bitset>
int main()
{
const int N = 8;
boost::dynamic_bitset<> a_out(0);
boost::dynamic_bitset<> b_out(0);
std::bitset<N>a(0x97); //10010111
std::bitset<N>b(0x72); //01110010
for (int i = 0; i < N; i++)
{
if (a[i] != b[i])
{
a_out.push_back(bool(a[i]));
b_out.push_back(bool(b[i]));
}
}
std::cout << a_out << "\n";
std::cout << b_out << "\n";
return 0;
}
输出:
10011
01100
[EDITED]
如果你想进行优化,你可以在for
循环之前添加它(但是你必须提升1.62或者更新才能使用reserve()
)
//@5gon12eder Optimization
const auto xorified = a ^ b;
const auto n = xorified.count();
a_out.reserve(n);
b_out.reserve(n);
在for
循环中比较位:
if (xorified[i]) { ... }
答案 2 :(得分:1)
您将需要编写自己的算法。这样的事情可能有用:
std::bitset<size> mask = a^b; //A zero will be put in place where a and b do match
int offset = 0;
std::bitset<size> fin(0); //This will hold the answer for the a bitset
for (int x = 0; x < size; x++)
{
if (!mask[x]) //If the bit is zero we are keeping the bit
{
if (a[x])
{
fin.set(offset);
}
offset++;
}
}
答案 3 :(得分:1)
编译时计算的所有内容
这里的其他答案都很棒,在一般情况下你应该更喜欢什么,因为你很可能不知道最初的两个比特集是什么。
然而,这没什么好玩的。对于您的具体示例,我们做有足够的信息在编译时解决所有问题,并使用constexpr if,variadic templates,variable template,和integer sequences * 我们可以在编译时执行所有计算和转换为字符串文字(用于初始化bitset)。
std::integer_sequence<int,1,0,0,1,0,1,1,1>
和std::integer_sequence<int,0,1,1,1,0,0,1,0>
std::integer_sequence<char, ...>
std::bitset
size()
成员函数从生成的std::integer_sequence<int, ...>
获取要创建的位集的大小:#include <iostream>
#include <utility>
#include <bitset>
// sequence concatenation
template <typename INT, INT ...s, INT ...t>
constexpr auto
concat_sequence(std::integer_sequence<INT,s...>,std::integer_sequence<INT,t...>){
return std::integer_sequence<INT,s...,t...>{};
}
// base case; empty sequence
template<class INT, INT a, INT b>
constexpr auto Filter(std::integer_sequence<INT, a>, std::integer_sequence<INT, b>)
{
if constexpr (a == b)
return std::integer_sequence<INT>{};
else
return std::integer_sequence<INT,a>{};
}
template<class INT>
constexpr auto Filter(std::integer_sequence<INT>, std::integer_sequence<INT>)
{
return std::integer_sequence<INT>{};
}
// recursive case
template<class INT, INT a, INT... b, INT c, INT... d>
constexpr auto Filter(std::integer_sequence<INT, a, b...>, std::integer_sequence<INT, c, d...> )
{
static_assert(sizeof...(b) == sizeof...(d), "Sequences should initially be the same length");
return concat_sequence(Filter(std::integer_sequence<INT, a>{}, std::integer_sequence<INT, c>{}),
Filter(std::integer_sequence<INT, b...>{}, std::integer_sequence<INT, d...>{}));
}
// for constructing bitset/printing
template <char... s>
using char_sequence=std::integer_sequence<char,s...>;
template <char ...s>
constexpr static char const make_char_string[]={s... , '\0'};
template <char ...s>
constexpr auto const & make_char_string_from_sequence(char_sequence<s...>){
return make_char_string<s...>;
}
template<class INT, INT digit>
constexpr auto make_binary_charseq()
{
static_assert(digit < 2, "binary digits are 0 and 1 only");
return char_sequence<digit == 1? '1' : '0'>{};
}
template <class INT, INT... elts>
struct convert_binary_to_charseq_impl;
template <class INT, INT n, INT ...rest>
constexpr auto convert_binary_to_charseq(std::integer_sequence<INT, n, rest...>){
return concat_sequence(make_binary_charseq<INT, n>(),
convert_binary_to_charseq_impl<INT, rest...>{}());
}
template <class INT, INT... elts>
struct convert_binary_to_charseq_impl{
constexpr auto operator()()const {
return convert_binary_to_charseq<INT, elts...>(std::integer_sequence<INT, elts...>{});
}
};
template <class INT>
struct convert_binary_to_charseq_impl<INT>{
constexpr auto operator()()const{
return char_sequence<>{};
}
};
和我们的测试:
int main()
{
using left_result = decltype(Filter(std::integer_sequence<int,1,0,0,1,0,1,1,1>{}, std::integer_sequence<int,0,1,1,1,0,0,1,0>{}));
using right_result = decltype(Filter(std::integer_sequence<int,0,1,1,1,0,0,1,0>{}, std::integer_sequence<int,1,0,0,1,0,1,1,1>{}));
static_assert(std::is_same_v<left_result, std::integer_sequence<int, 1,0,0,1,1>>, "Filtering did not work");
static_assert(std::is_same_v<right_result, std::integer_sequence<int, 0,1,1,0,0>>, "Filtering did not work");
std::bitset<left_result::size()> a(make_char_string_from_sequence(convert_binary_to_charseq(left_result{})));
std::bitset<right_result::size()> b(make_char_string_from_sequence(convert_binary_to_charseq(right_result{})));
std::cout << a << std::endl;
std::cout << b << std::endl;
}
输出:
10011
01100
这方面的缺点是我有效地进行了两次计算,但我确信它可以重做(这都是在编译时所以我们不在乎,对吧!?)
* 信用到期的信用:Peter Sommerlad的CppCon2015谈话对于将序列转换为字符串非常宝贵。 Slides
答案 4 :(得分:0)
如果你正在使用std :: bitset,你可以先使用XOR运算符。 这将为您提供新的bitset,在值相同的索引上填充0,否则为1。 之后,您只需删除新bitset为0的索引。
答案 5 :(得分:0)
您无法从std::bitset
中移除位,因此您的结果会有额外的零。我的意思是结果而不是10011
将是00010011
constexpr int num = 8;
std::bitset<num> a("10010111");
std::bitset<num> b("01110010");
std::bitset<num> a_result;
std::bitset<num> b_result;
unsigned int last_index = 0;
for(auto index = 0; index < num; ++index)
{
if(a.test(index) ^ b.test(index))
{
a_result.set(last_index, a.test(index));
b_result.set(last_index, b.test(index));
++last_index;
}
}
或者您可以使用std::vector<bool>
作为结果,这是std::vector
bool
在内部使用位集的专门化(实际上是它的实现定义)。所有可能的解决方案都取决于您希望实现的目标。
constexpr int num = 8;
std::bitset<num> a("10010111");
std::bitset<num> b("01110010");
std::vector<bool> a_result;
std::vector<bool> b_result;
for(auto index = 0; index < num; ++index)
{
if(a.test(index) ^ b.test(index))
{
a_result.push_back(a.test(index));
b_result.push_back(b.test(index));
}
}
答案 6 :(得分:0)
您尝试使用此算法
void Procedure(void)
{
unsigned char NumA, NumB;
unsigned char ResA = 0, ResB = 0;
int Count1 = 0;
int Count2 = 8;
NumA = 0x97; // 10010111
NumB = 0x72; // 01110010
while( Count1 < 8 )
{
if( (NumA & 0x80) != (NumB & 0x80) )
{
ResA = ResA << 1;
if( (NumA & 0x80) == 0x80)
ResA = ResA | 0x01;
ResB = ResB << 1;
if( (NumB & 0x80) == 0x80)
ResB = ResB | 0x01;
--Count2;
}
NumA = NumA << 1;
NumB = NumB << 1;
++Count1;
}
ResA = ResA << Count2;
ResB = ResB << Count2;
}
结果存储在ResA和ResB变量
中答案 7 :(得分:0)
这是我的 C++ 解决方案:
#include <iostream>
#include <bits/stdc++.h>
pair<int, int> extractMatchingBits(int a, int b) {
int cleanA = 0;
int cleanB = 0;
int matches = a^b;
for (int i = 0; matches != 0; i++) {
const int bitIdx = log2(matches & -matches);
cleanA |= ((a >> bitIdx) & 1) << i;
cleanB |= ((b >> bitIdx) & 1) << i;
matches &= matches - 1;
}
return make_pair(cleanA, cleanB);
}
答案 8 :(得分:-1)
您不能拥有bitset
类型的结果,因为您必须在编译时设置位集大小,而实际上您并不知道位数相等的位数。