我有以下代码:
(defn BitScanReverse [^Long bit-board]
(loop [value bit-board r 0]
(cond
(> value 0x00000000FFFFFFFF) (recur (unsigned-bit-shift-right value 32) (+ r 32))
(> value 0x000000000000FFFF) (recur (unsigned-bit-shift-right value 16) (+ r 16))
(> value 0x00000000000000FF) (recur (unsigned-bit-shift-right value 8) (+ r 8))
(> value 0x000000000000000F) (recur (unsigned-bit-shift-right value 4) (+ r 4))
(> value 0x0000000000000003) (recur (unsigned-bit-shift-right value 2) (+ r 2))
(> value 0x0000000000000001) (recur (unsigned-bit-shift-right value 1) (+ r 1))
:else r)))
它返回在位板中找到的最后一位的索引。 问题是当我尝试运行时: (BitScanReverse 18446462598732840960);;期待63。 它给了我: IllegalArgumentException超出范围的值:18446462598732840960 clojure.lang.RT.longCast(RT.java:1134)
这个位板是黑色棋子的初始位置。问题是在clojure(以及java)中签署了long。我曾尝试使用BigInt,但它不允许进行位操作。
有什么建议吗?
答案 0 :(得分:0)
这是使用位测试而不是循环的快速且非常脏的反向扫描实现,这可能会或可能不会更有效。
(defn rev-test [^long n ^long x] (bit-test x n))
(defn BitScanReverse [^long bit-board](condp rev-test bit-board
0 0,1 1,2 2,3 3,4 4,5 5,6 6,7 7,8 8,9 9,10 10,11 11,12 12,13 13,14 14,15 15,16 16,17 17,18 18,19 19,20 20,21 21,22 22,23 23,24 24,25 25,26 26,27 27,28 28,29 29,30 30,31 31,32 32,33 33,34 34,35 35,36 36,37 37,38 38,39 39,40 40,41 41,42 42,43 43,44 44,45 45,46 46,47 47,48 48,49 49,50 50,51 51,52 52,53 53,54 54,55 55,56 56,57 57,58 58,59 59,60 60,61 61,62 62,63 63))
这将最低有效位视为0,就像比特测试一样,就像0索引数组一样,所以我认为它与你的实现不一样。在生成输入时,您将被限制为具有正符号文字的63位,但您仍然可以将符号位用作第64位。尝试使用一个辅助方法来构造所需的数字,并使用稍高的抽象级别,例如这个fn将最高有效32位和最低有效32位作为两个args。这可能写成一个宏,但我没有足够的经验写一个,并确保它会工作。
(defn bitboard [^long upper ^long lower]
(bit-or (bit-shift-left upper 32)
(bit-and lower 0xffffffff)))
重要的是,对于性能,^ Long是盒装的,我认为^ long可能不会在适当的情况下装箱。数字原始数组是我发现的少数情况之一,其中原语确实是它们应该在JVM上的内容(字节数组将始终是一个字节数组,具有连续的8位内存块,但声明了一个字节由于对齐优化,即使在Java中,也可以占用超过8位的内存。我强烈建议使用ztellman的primitive-math库来查找Clojure中数学需要反射的情况,这种情况经常出现,对于像这样的位错误代码非常重要。
答案 1 :(得分:0)
使用Java BitSet
作为@assylias建议,给出一个简单的解决方案:
(import 'java.util.BitSet)
(defn bit-scan-reverse [^long bit-board]
(let [board (BitSet/valueOf (long-array [bit-board]))]
(.previousSetBit board 63)))
修改强>
由于long
已签名,因此上述解决方案无效。继续使用BitSet
进行搜索,我提出了:
(defn bit-scan-reverse2 [bit-board]
(let [board (-> (biginteger bit-board) ; coerce to BigInteger
.toByteArray ; as byte[] big-endian
reverse ; to little-endian
byte-array
BitSet/valueOf)
max-index (.size board)]
(.previousSetBit board max-index)))
哪种方法很好但看起来很复杂。然后查看BigInteger
doc,我找到了实际回答问题的bitLength()
方法:
bitLength():返回此BigInteger的最小二进制补码表示中的位数,不包括符号位。
由于我们只关心电路板表示的正数,所以可以使用这个bitLength()
方法找到64位电路板中最左边的位:
(defn bit-scan-reverse3 [bit-board]
(-> bit-board
biginteger
.bitLength
dec))
(map bit-scan-reverse3 '(0xFFFF000000000000 0x0 0x1 0xF 0xFFF))
user> 63 -1 0 3 11
**结束编辑**
在性能方面,我测试了3个版本,它为这个快速工作台提供了非常相似的时间(~10ns)。 BitScanReverse2
是@notostraca提供的解决方案:
(require '[clojure.data.generators :as gen])
(require '[criterium.core :as c])
(let [bunch-of-longs (doall (take 10000 (filter (partial < 0)
(repeatedly gen/long))))]
(c/quick-bench (map BitScanReverse bunch-of-longs))
(c/quick-bench (map BitScanReverse2 bunch-of-longs))
(c/quick-bench (map bit-scan-reverse bunch-of-longs))
(c/quick-bench (map bit-scan-reverse2 bunch-of-longs))
(c/quick-bench (map bit-scan-reverse3 bunch-of-longs)))