循环遍历整数中的位,ruby

时间:2012-06-06 09:45:56

标签: ruby loops integer bit

我正在编写一个程序,其中一个问题是我需要对某些整数中的位模式进行一些分析。

因此,我希望能够做到这样的事情:

#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

8 个答案:

答案 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(即BignumFixnum 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))。

下面是算法的步骤,但是许多读者发现,通过首先阅读以下部分(“插图”),可以更轻松地进行操作。

  1. longest设置为0
  2. len设置为等于nlen = n.bit_length)中的位数。
  3. 如果为len <= 2,请返回答案(012)。
  4. 确定midnmid = len/2)的中间位的索引mid = len >> 1
  5. 设置ri = mid+1li = mid-1
  6. 如果中间位(n[mid])的值为零,请转到步骤10。
  7. ({n[mid] = 1进行此步骤。)确定最大索引li >= mid和最小索引ri <= mid,以使n[i] = 1适用于所有ri <= i <= li
  8. 设置longest = [longest, ri-li+1].maxli += 1ri -= 1
  9. 如果li == len转到步骤10;否则将ln设置为等于索引li(最低有效)至len-1(最高有效)处的位数。递归设置为nln,然后转到步骤2。这将返回数字lncand)中最长的1位字符串。设置longest = [longest, cand].max
  10. 如果ri < 0转到步骤11;否则将rn设置为等于索引0(最低有效)至ri(最高有效)处的位数。递归设置为nrn,然后转到步骤2。这将返回编号为rn ( cand ). Set longest = [longest,cand]的最长1位字符串。 .max`。
  11. 递归返回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

我们现在必须检查0b101001100b1011110(第二个数字中的前导零会消失)。

对于左边的数字,我们计算以下内容。

n = 0b10100110
len = n.bit_length
  #=> 8
mid = len >> 1
  #=> 4
n[mid]
  #=> 0

所以我们有

101 0 0110

(注意n[0]是最低有效位)。我们可以排除0b1010b110,因为它们都有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]]