Зацикливание через биты в целое число, рубин

Я создаю программу, в которой одна из проблем заключается в том, что мне нужно выполнить некоторый анализ битового шаблона в некоторых целых числах.

Из-за этого я хотел бы сделать что-то вроде этого:

#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

Ответ 1

Чтобы определить длину самой длинной последовательности последовательных 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

Ответ 2

Помните, что o и "0" имеют логическое значение true в ruby, поэтому "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

Ответ 3

В Ruby Integer (то есть оба 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 nifty Enumerable:

0b10111110.chunk {|b| true if b == 1 }.map(&:last).max_by(&:size).size - 1

(Или 0b10111110.to_a.chunk …, если вы предпочитаете менее интрузивный метод.)

Если вы беспокоитесь о производительности, механизм выполнения, который вы выбираете, имеет большое значение. Оптимизирующий компилятор Rubinius или JRuby может встраивать и оптимизировать многие вызовы методов, которые, например, YARV довольно простой компилятор. Специальное лечение YARV Fixnum может дать ему преимущество перед МРТ.

Как вы можете видеть из примеров, я являюсь большим поклонником бессмысленного стиля и функционального программирования. Если вы можете доказать с помощью профилирования, что у вас есть узкое место в определенной точке кода, вам может потребоваться заменить его чуть менее изящной или нечистой версией, или вы можете использовать предохранители map и 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

или

0b10111110.chunk {|b| true if 1 == b }.max_by {|key, chunk| chunk.size }.last.size - 1

Ответ 4

Ruby не может быть хорошим выбором для вашего проекта. Сила рубина - это не производительность, а то, что он позволяет вам делать такие вещи, как:

n.to_s(2).scan(/1+/).sort.last.length - 1

вместо написания гор кода. На самом деле почти любой другой язык, скорее всего, будет работать лучше, если вы не возражаете писать сложный код (который вам кажется не так).

Ответ 5

Если вы ищете производительность, то построение справочной таблицы, вероятно, будет наиболее эффективным. Особенно, если вы делаете это в плотной петле:

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

Ответ 6

Иногда использование строк является наиболее очевидным методом, и производительность допустима:

def oneseq(n)
  n.to_s(2).split(/0+/).sort_by(&:length).last.to_s.length
end