我正在编写一个程序,其中一个问题是我需要对某些整数中的位模式进行一些分析。
因此,我希望能够做到这样的事情:
#Does **NOT** work:
num.each_bit do |i|
#do something with i
end
通过这样做,我能够创造出有效的东西:
num.to_s(2).each_char do |c|
#do something with c as a char
end
然而,这并不是我想要的性能。
我发现你可以这样做:
0.upto(num/2) do |i|
#do something with n[i]
end
这比each_char
方法
这个循环将被执行数百万次或更多次,所以我希望它尽可能快。
供参考,这是函数的全部内容
@@aHashMap = Hash.new(-1)
#The method finds the length of the longes continuous chain of ones, minus one
#(101110 = 2, 11 = 1, 101010101 = 0, 10111110 = 4)
def afunc(n)
if @@aHashMap[n] != -1
return @@aHashMap[n]
end
num = 0
tempnum = 0
prev = false
(n.to_s(2)).each_char do |i|
if i
if prev
tempnum += 1
if tempnum > num
num = tempnum
end
else
prev = true
end
else
prev = false
tempnum = 0
end
end
@@aHashMap[n] = num
return num
end
答案 0 :(得分:12)
要确定连续1的最长序列的长度,这会更有效:
def longest_one_chain(n)
c = 0
while n != 0
n &= n >> 1
c += 1
end
c
end
该方法只计算你可以“按位与”数字的次数,自身向右移1位,直到它为零。
示例:
______ <-- longest chain
01011011100001111110011110101010 c=0
AND 0101101110000111111001111010101
1001100000111110001110000000 c=1, 1’s deleted
AND 100110000011111000111000000
100000011110000110000000 c=2, 11’s deleted
AND 10000001111000011000000
1110000010000000 c=3, 111’s deleted
AND 111000001000000
110000000000000 c=4, 1111’s deleted
AND 11000000000000
10000000000000 c=5, 11111’s deleted
AND 1000000000000
0 c=6, 111111’s deleted
答案 1 :(得分:3)
请注意o和“0”在ruby中的布尔值都为true,因此“if i
”不会给出您想要的结果。
将每个数字转换为字符串当然是应该避免的。
Fixnum
有一个方法[]
来访问该数字的位,因此这有可能更快。
如果您已尝试使用
0.upto(num/2) do |i|
#do something with n[i]
end
然后num/2
可能太大了,所以你经常循环。
对于32位整数,您应该使用
0.upto(31) do |i|
if n[i] == 1
...
end
end
答案 2 :(得分:3)
Ruby可能不是您项目的好选择。 红宝石的力量不是它的表现,而是它可以让你做的事情如下:
n.to_s(2).scan(/1+/).sort.last.length - 1
而不是写大量的代码。如果你不介意编写复杂的代码(你似乎并不这样做),那么任何其他语言都可能表现得更好。
答案 3 :(得分:3)
在Ruby中,Integer
s(即Bignum
和Fixnum
s)已经被编入索引,就好像它们是位数组一样。但是,它们不是Enumerable
。
但你可以解决这个问题,当然:
class Integer
include Enumerable
def each
return to_enum unless block_given?
(size*8).times {|i| yield self[i] }
end
end
稍微不那么干扰的方式可能是将Integer
表示为数组:
class Integer
def to_a
Array.new(size*8, &method(:[]))
end
end
然后你可以使用Ruby的漂亮Enumerable
方法:
0b10111110.chunk {|b| true if b == 1 }.map(&:last).max_by(&:size).size - 1
(或0b10111110.to_a.chunk …
如果您更喜欢较少侵入性的方法。)
如果您担心性能,您选择的执行引擎会产生很大的不同。例如,Rubinius或JRuby的优化编译器可能能够内联和优化YARV相当简单的编译器无法进行的许多方法调用。 YARV对Fixnum
的特殊处理可能使其优于MRI。
从示例中可以看出,我是无点样式和函数式编程的忠实粉丝。如果您可以通过分析证明代码中的某个特定点存在瓶颈,则可能需要将其替换为稍微不那么优雅或不纯的版本,或者您可能希望手动融合map
和{ {1}}。
max_by
或
class Integer
def to_a
Array.new(size*8) {|i| self[i] }
end
end
0b10111110.chunk {|b| true if 1 == b }.map {|key, chunk| chunk.size }.max - 1
答案 4 :(得分:1)
如果您正在寻找性能,那么构建查找表可能是最高效的方式。特别是如果你在紧密循环中这样做:
class BitCounter
def initialize
@lookup_table = (0..65535).map { |d| count_bits(d) }
end
def count(val)
a,b,c = @lookup_table[val & 65535]
d,e,f = @lookup_table[val >> 16]
[a,b,c+d,e,f].max
end
private
def count_bits(val)
lsb = lsb_bits(val)
msb = msb_bits(val)
[lsb, inner_bits(val, lsb, msb), msb]
end
def lsb_bits(val)
len = 0
while (val & 1 == 1) do
val >>= 1
len += 1
end
len
end
def msb_bits(val)
len = 0
while (val & (1<<15) == (1<<15)) do
val <<= 1
len += 1
end
len
end
def inner_bits(val, lsb, msb)
lens = []
ndx = lsb
len = 0
(lsb+1..(15-msb)).each do |x|
if ((val & (1<<x)) == 0)
if(len > 0)
lens << len
len = 0
end
else
len += 1
end
end
lens.max || 0
end
end
然后是一个例子:
counter = BitCounter.new
p counter.count 0b01011011100001111110011110101010 // 6
这基本上为所有16位值创建一个循环表,然后计算这些缓存值的最大结果。
你甚至可以结合更具表现力的n.to_s(2).scan(/1+/).sort.last.length - 1
形式,而不是在表初始化中使用按位逻辑,因为它不再是瓶颈点 - 虽然我会坚持使用逐位数学只是为了清晰的表达而不是比字符串解析。每次查询只需要2个表查找,一个加法和max
答案 5 :(得分:1)
有时使用字符串是最明显的方法,性能是可以容忍的:
def oneseq(n)
n.to_s(2).split(/0+/).sort_by(&:length).last.to_s.length
end
答案 6 :(得分:0)
算法
一个人可能会考虑使用二进制搜索。我实现了以下算法,以确定给定的非负整数n
中1位最长的字符串的长度。计算复杂度为O({n
),但对于n
的许多值,它将接近O(log 2 (n
))。
下面是算法的步骤,但是许多读者发现,通过首先阅读以下部分(“插图”),可以更轻松地进行操作。
longest
设置为0
。len
设置为等于n
(len = n.bit_length
)中的位数。len <= 2
,请返回答案(0
,1
或2
)。mid
(n
或mid = len/2
)的中间位的索引mid = len >> 1
。ri = mid+1
和li = mid-1
。n[mid]
)的值为零,请转到步骤10。n[mid] = 1
进行此步骤。)确定最大索引li >= mid
和最小索引ri <= mid
,以使n[i] = 1
适用于所有ri <= i <= li
。longest = [longest, ri-li+1].max
,li += 1
和ri -= 1
。li == len
转到步骤10;否则将ln
设置为等于索引li
(最低有效)至len-1
(最高有效)处的位数。递归设置为n
到ln
,然后转到步骤2。这将返回数字ln
(cand
)中最长的1位字符串。设置longest = [longest, cand].max
。ri < 0
转到步骤11;否则将rn
设置为等于索引0
(最低有效)至ri
(最高有效)处的位数。递归设置为n
至rn
,然后转到步骤2。这将返回编号为rn (
cand ). Set
longest = [longest,cand]的最长1位字符串。 .max`。longest
。插图
假设
n = 0b1010011011101011110
#=> 341854
然后
len = n.bit_length
#=> 19
mid = len >> 1
#=> 9
n[mid]
#=> 1
longest = 0
我们可以如下描绘n
101001101 1 101011110
其中中间位1
突出。由于它是1
,因此我们在左右查找1的序列。我们获得以下内容。
10100110 111 01011110
我们发现三个1,因此我们更新longest
。
longest = [longest, 3].max
#=> [0, 3].max => 3
我们现在必须检查0b10100110
和0b1011110
(第二个数字中的前导零会消失)。
对于左边的数字,我们计算以下内容。
n = 0b10100110
len = n.bit_length
#=> 8
mid = len >> 1
#=> 4
n[mid]
#=> 0
所以我们有
101 0 0110
(注意n[0]
是最低有效位)。我们可以排除0b101
和0b110
,因为它们都有3
位,这不超过longest
的当前值3
。 / p>
现在我们考虑右边的一半,
n = 0b1011110
len = n.bit_length
#=> 7
mid = len >> 1
#=> 3
n[mid]
#=>1
所以我们有
101 1 110
作为n[mid] #=> 1
,我们左右寻找1的字符串并获得
10 1111 0
当我们发现一个4
1的字符串时,我们设置
longest = [longest, 4].max
#=> [3, 4].max = 4
最后,我们看到左侧(2
)和右侧(1
)的位数均小于3
,因此我们完成了操作,然后返回{ {1}}。 (OP实际上需要longest #=> 4
,我们将其视为附带计算结果。)
代码
longest - 1 #=> 3
示例
def longest_run(n, longest=0)
len = n.bit_length
return [longest, (n & 1) + (n >> 1)].max if len <= 2
mid = len >> 1
ri = mid-1
li = mid+1
if n[mid] == 1
until n[ri] == 0
ri -= 1
end
until n[li] == 0
li += 1
end
cand = li-ri-1
longest = cand if cand > longest
end
if ri >= 0
shift = ri+1
m = n ^ ((n >> shift) << shift)
if m.bit_length > longest
cand = longest_run(m, longest)
longest = cand if cand > longest
end
end
if li <= len - 1
m = n >> li
if m.bit_length > longest
cand = longest_run(m, longest)
longest = cand if cand > longest
end
end
longest
end
答案 7 :(得分:0)
这里有一些更简单的方法(尽管我期望@Steven的答案,而我的其他答案会更有效)。
#1
def max_string_of_ones(n)
max_length = 0
cand = 0
(0..n.bit_length).reduce(0) do |max_length, i|
if n[i] == 1
cand += 1
else
max_length = cand if cand > max_length
cand = 0
end
max_length
end
end
注意n[n.bit_length] #=> 0
。
#2
第二种方法使用Ruby的flip-flop operator。另外,我想:“如果Integer
有一个each_bit
方法返回一个枚举数,是否会方便?”,所以我添加了一个。
class Integer
def each_bit
Enumerator.new do |yielder|
if block_given?
bit_length.times { |i| yielder << yield(self[i]) }
else
bit_length.times { |i| yielder << self[i] }
end
end
end
end
def max_string_of_ones(n)
n.each_bit.slice_before { |b| true if b==0 .. b==1 }.max_by(&:size).size
end
max_string_of_ones(0b1100011101111011100)
#=> 4
请注意以下中间计算。
def max_string_of_ones(n)
n.each_bit.slice_before { |b| true if b==0 .. b==1 }
end
enum = max_string_of_ones(0b1100011101111011100)
#=> #<Enumerator: #<Enumerator::Generator:0x00000000019a2f80>:each>
enum.to_a
#=> [[0], [0], [1, 1, 1], [0], [1, 1, 1, 1], [0],
# [1, 1, 1], [0], [0], [0], [1, 1]]