繁体   English   中英

快速查找两组数字之间的交集,一组由按位条件定义,另一组由算术条件定义

[英]Fast way to find a intersection between two sets of numbers, one defined by a bitwise condition and another by an arithmetic condition

这可能是很好的覆盖,但我对这个问题一无所知,所以我将使用业余术语。 假设我正在搞乱一些条件,每个条件都在int中定义一组非零数字,我们只说8位int。 所以对于一个按位,我可能有这个:

11XX00XX

说我希望所有字节都有1s,其中有1s,0s有0,并且不关心Xs。 所以11110000或11000010实现了这一点,但01110000却没有。 够容易吧? 对于算术条件,我只能想象使用==,!=,> =,>,<=或<与常数进行比较。 所以我可以说:

X > 16

所以任何大于16的数字(00010000)。 如果我想查找上述示例集中的所有数字,该怎么办? 我可以通过观察它看出任何以100XX结尾的数字都符合要求,因此交叉点的按位部分包括11X100XX。 然后我必须包括区域111X00XX以填充其上方的其余范围。 对? 虽然我认为对于其他情况,它不会如此整洁,正确吗? 无论如何,对于任何这些算术条件与任何可能的那些按位算法相比,这背后的算法是什么。 当然必须有一般情况!

假设有一个,它可能是显而易见的,如果事情变得更复杂怎么办? 如果我的按位要求变为:

11AA00XX

标有A的任何东西必须相同。 所以110000XX或111100XX,但不是111000XX。 对于任意数量和任何位置的任意数量的相同位“类型”(A,B,C等),通过某种算术比较求解交点的最佳方法是什么? 有吗?

我正在考虑这些按位运算是一种单一的比较运算/分支,就像算法的设置一样。 所以也许一个是所有常数,当某个字节B 01110000与它们进行AND运算时,会产生00110000.因此,常数区域,即我的“按位条件”,将是X011XXXX,因为X011XXXX AND 01110000 = 00110000我所有的“按位条件”都是通过像AND,OR和XOR这样的操作的反转形成的。 不确定是否会包含像NAND这样的东西。 这可能会限制实际可能的条件,也许? 如果是这样,那么我不关心那些类型的条件。

很抱歉蜿蜒的尝试解释。 我正在做什么名字? 看起来它在CS中已经很好用了,所以一个名字可以让我对这个主题进行一些很好的阅读。 但我主要只是寻找一个好的算法来解决这个问题。 我最终会在十字路口使用两个以上的东西(可能有几十个或更多),所以解决它的方法可以很好地扩展。

按位

好的,所以我们看一下按位操作,因为这是做你想要的最有效的方法。 为清楚起见(和参考),转换为十进制的按位值是

00000001 =   1
00000010 =   2
00000100 =   4
00001000 =   8
00010000 =  16
00100000 =  32
01000000 =  64
10000000 = 128

现在,给定变量x上的11XX00XX位模式,我们将执行以下检查:

x AND 128 == true
x AND 64  == true
x AND 8   == false
x AND 4   == false

如果所有这些条件都为真,则该值与模式匹配。 基本上,您正在检查以下条件:

1XXXXXXX AND 10000000 == true
X1XXXXXX AND 01000000 == true
XXXX0XXX AND 00001000 == false
XXXXX0XX AND 00000100 == false

把它放在编程语言的说法中(我将使用C#),你会寻找

if ((x && 128) && (x && 64) && !(x && 8) && !(x && 4))  
{
    // We have a match
}

对于更复杂的11AA00XX位模式,您将添加以下条件:

NOT ((x AND 32) XOR (x AND 16)) == true

这样做首先检查x AND 32 ,根据x中该位的值返回0或1。 然后,它对另一个位x AND 16进行相同的检查。 XOR操作检查位的差异,如果位是不同的则返回1,如果位相同则返回0。 从那里开始,因为我们想要返回1,如果它们是相同的,我们NOT整个条款。 如果位相同,则返回1。


算术

在算术方面,您将看到使用除法和模运算的组合来隔离有问题的位。 要与除法一起工作,首先要找到数字可以除以的最大二次幂。 换句话说,如果你有x=65 ,那么2的最高功率是64。

完成除法之后,然后使用模数来取除除法后的余数。 如上例所示,给定x=65x MOD 64 == 1 使用该数字,您可以重复之前的操作,找到最高的2的幂,并继续直到模数返回0。

在saluce的答案上稍微扩展一下:

比特测试

您可以构建测试模式,因此您不需要单独检查每个位(测试整数比测试一位一次更快,特别是一次性测试整个数字就像好):

testOnes = 128 & 64 // the bits we want to be 1
testZeros = ~(8 & 4) // the bits we want to be 0, inverted

然后以这种方式执行测试:

if (!(~(x & testOnes) & testOnes) &&
    !(~(~x | testZeros))) {
  /* match! */
}

逻辑解释说

首先,在testOnestestZeros您将感兴趣的位设置为1,其余为0。

testOnes testZeros
11000000 11110011

然后x & testOnes将把我们不想测试的所有位清零为零(注意&&&之间的区别: &按位执行逻辑AND运算,而&&是整数的逻辑AND )。

testOnes
11000000
x        x & testOnes
11110000 11000000
11000010 11000000
01010100 01000000

现在最多我们测试为1的位可以是1,但是我们不知道它们是否都是1:通过反转结果( ~(x & testOnes) ),我们得到所有我们不关心的数字关于1和我们想要测试的位是0(如果它们是1)或1(如果它们是0)。

testOnes
11000000
x        ~(x & testOnes)
11110000 00111111
11000010 00111111
01010100 10111111

通过bitwise- AND用-ing它testOnes我们得到0,如果位,在利益均全1 x ,和非否则为零。

testOnes
11000000
x        ~(x & testOnes) & testOnes
11110000 00000000
11000010 00000000
01010100 10000000

此时我们有:0如果我们想要测试1的所有位实际上都是1,否则非0,所以我们执行逻辑NOT以将0变为true而将非0变为false

x        !(~(x & testOnes) & testOnes)
11110000 true
11000010 true
01010100 false

零位测试类似,但我们需要使用按位 - OR| ),而不是按位 - AND& )。 首先,我们翻转x ,所以should-be-0位变为should-be-1,然后OR -ing将所有非兴趣位变为1,同时保持其余位; 所以在这一点上,如果x中的0位实际上是0,那么我们有全1,而非全1,否则,所以我们再次翻转结果,在第一种情况下获得0而在非0中第二。 然后我们应用逻辑NOT! )将结果转换为true (第一种情况)或false (第二种情况)。

testZeros
11110011
x        ~x       ~x | testZeros ~(~x | testZeros) !(~(~x | testZeros))
11110000 00001111 11111111       00000000          true
11000010 00111101 11111111       00000000          true
01010100 10101011 11111011       00000100          false

注意:您需要意识到我们已经为每个测试执行了4次操作,因此共计8次。 根据您要测试的位数,这可能仍然小于单独检查每个位。

算术测试

对等式/差异的测试很容易:对测试的数字进行XOR - 如果所有位相等则得到0(因此数字相等),如果至少有一位不同则得到0(因此数字不同) 。 (应用NOT将相等的测试结果设为true ,将差异设为false 。)

然而,为了测试不平等,大多数时候你都不幸运,至少它适用于逻辑运算。 你是正确的,检查的权力,以2的(例如,在你的问题16),可与逻辑运算来完成(bitwise- AND和测试0),但对于某些数字不是掌权者的-2,这是不是太简单。 例如,让我们测试x>5 :模式是00000101,那么你如何测试? 如果它在第5个最高有效位中具有1,则该数字更大,但是6(00000110)也更大,所有前5位为0。

您可以做的最好的事情是测试该数字是否至少是该数字中2的最大功率的两倍(上例中为4为5)。 如果是,那么它比原来大; 否则,它必须至少与数字中最高2的幂一样多,然后对不太重要的位执行相同的测试。 如您所见,根据测试编号中的1位数,操作数是动态的。

链接位

在这里, XOR是你的朋友:对于两位XOR如果相同则产生0,否则为1。

我不知道执行测试的简单方法,但以下算法应该有所帮助:

假设您需要位b1 ,..., bn相同(全1或全0),然后将所有其他位清零(参见逻辑 - AND上面的),然后隔离测试模式中的每个位,然后排列它们在相同的位置(为方便起见,让它成为最不重要的位置)。 然后对它们中的两个进行XOR ,然后对结果等的第三个进行XOR ,将在每个奇数步骤产生偶数,如果原始数字中的位相同,则在每个偶数步骤产生奇数。 您将需要在每个步骤进行测试,因为测试只有最终结果可能不正确,因为大量的待测链接位。

testLinks
00110000
x        x & testLinks
11110000 00110000
11000010 00000000
01010100 00010000

x        x's bits isolated isolated bits shifted
11110000 00100000          00000001
         00010000          00000001
11000010 00000000          00000000
         00000000          00000000
01010100 00000000          00000000
         00010000          00000001

x        x's bits XOR'd result
11110000 00000000       true (1st round, even result)
11000010 00000000       true (1st round, even result)
01010100 00000001       false (1st round, odd result)

注意:在类C语言中, XOR运算符是^

注意:如何将位排到同一位置? 比特移位。 Shift-left( << )将所有位向左移位,“丢失”最高有效位并将“0”引入最低有效位,基本上将该数字乘以2; shift-right( >> )操作类似,向右移位,基本上整数除以2,但是,它将相同的位“引入”到已存在的最高位(因此保持负数)负)。

TLDR saluce的答案:

按位检查分别考虑各个位,算术检查将所有位一起考虑。 确实如此,它们与2的幂相吻合,但不适用于任意数字。

因此,如果您同时拥有这两者,则需要实施两组检查。

32位int的所有可能值的空间有点大,因此您每次都必须检查它们。 只需确保使用短路来消除重复检查,例如x> 5 || x> 3。

您已经定义了一个适合指定掩码的DSL。 我会写一个解析器来读取该掩码并执行特定于每个唯一字符的操作。

AABBB110 =面具

步骤1:将所有唯一字符提取到数组[01AB]中。 您可以省略'X',因为不需要任何操作。

第2步:遍历该数组,将文本掩码处理为单独的位掩码,每个唯一字符对应一个,将该字符位置的位替换为1,将所有其他位替换为0。

Mask_0 = 00000001 = 0x01
Mask_1 = 00000110 = 0x06
Mask_A = 11000000 = 0xC0
Mask_B = 00111000 = 0x38

步骤3:将每个掩模传递到下面定义的适当功能。

boolean mask_zero(byte data, byte mask) {
  return (data & mask) == 0;
}

boolean mask_one(byte data, byte mask) {
  return (data & mask) == mask;
}

boolean mask_same(byte data, byte mask) {
  byte masked=data & mask;
  return (masked==0) || (masked==mask);
} 

您希望结果集的格式是什么? 算术集(让我们称之为A)和按位集(让我们称之为B)都具有可快速测试的优点,以及易于迭代的优点。 但是这些定义中的每一种都可以定义另一种不能定义的东西,因此它们的交集需要完全不同。

我要做的是分别处理测试和迭代。 通过简单地使用逻辑“和”,可以通过将两个集合转换为任意数学表达式(按位集合可以转换为几个按位操作,如其他海报所描述的)来轻松创建易于测试的定义。 这很容易推广到任何类型的集合 - 只是存储对两个父集合的引用,当被问及两个集合中是否存在数字时,只需检查两个父集合。

但是,任意数学表达式都不容易迭代。 对于迭代,最简单的方法是迭代集合B(可以通过仅更改不受集合约束的位来完成),并允许集合A约束结果。 如果A使用>或> =,则向下迭代(从最大数字开始)并在false时停止以获得最大效率; 如果A使用<或<=,则迭代(从最小数字开始)并停止为false。 如果A使用==,那么只有一个要检查的数字,如果A使用!=,那么任何一个方向都可以(但你不能停止为假)。

请注意,按位集的行为类似于可索引的数字数组 - 例如,11XX00XX定义的按位集可视为索引范围为0000到1111的数组,索引的位适合相应的插槽。 这使得在集合上向上或向下迭代变得容易。 集合A可以以类似的方式编制索引,但由于它可以很容易地成为无限集(除非受机器的int值约束,尽管它不必如此,即BigInteger),迭代不是最安全的事情。过度。

暂无
暂无

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

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