Ruby: подсчитать количество единиц в двоичном числе - PullRequest
6 голосов
/ 28 октября 2009

У меня есть двоичное число (52 бита), представленное в виде строки "01100011 ...."

Какой самый быстрый способ подсчитать количество единиц?

"01100011....".count("1") 

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

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

def bit_vec(str)
    alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    bv = ""
    alphabet.each_char do |a|
        if str.include?(a)
            bv += "1"
        else
            bv += "0"
        end
    end
        bv
end

Метод bit_vec вызывается около 170К раз. Я храню битовые векторы в хэше и использую их для поиска похожих слов для данного слова, используя XOR для битовых векторов и считая количество единиц (больше 1 = меньше сходства). Если метод count не использует String # scan, что еще можно использовать?

Я знаю, что Ruby медленнее, чем, скажем, C или Java. Я просто пытаюсь улучшить алгоритм как можно лучше. Я не ищу грубую скорость.

Может быть, включить? метод является узким местом?

Ответы [ 5 ]

10 голосов
/ 28 октября 2009

Вы получите производительность O(n), несмотря ни на что. Попробуйте эту простую команду ruby. Мера, если это действительно проблема.

Этот простой скрипт, измеренный с помощью time ruby test.rb, занял 0,058 секунды ЦП. Это на старом процессоре с частотой 1,25 ГГц. Вы действительно уверены, что эта операция слишком медленная?

10000.times do
  "0100010000100001111101101000111110000001101001010".count("1")
end

Если это не достаточно быстро, напишите расширение C. Старайтесь избегать использования условных выражений. Напишите это так:

count = 0;
for (i = stringLength; i; i++) {
    count += string[i] - '0';     // no conditional used.
}

Но, честно говоря, если вам нужна такая скорость, рубин - не тот язык, который вам подходит. В ruby ​​столько разных вещей, которые занимают намного больше времени, чем простое .count("1").

9 голосов
/ 29 октября 2009

Обратите внимание, что проблема подсчета 1-бит называется «подсчет населения».

По крайней мере в Ruby, придерживайтесь обработки их как строки с помощью метода count, если только у вас нет веских причин использовать целые числа.

count

Тест: 78,60 с для 10 000 000 итераций (127 225,63 итераций в секунду)

Для целых чисел,

Если вас не интересуют значения выше 2**32,

def popcount(x)
  m1 = 0x55555555
  m2 = 0x33333333
  m4 = 0x0f0f0f0f
  x -= (x >> 1) & m1
  x = (x & m2) + ((x >> 2) & m2)
  x = (x + (x >> 4)) & m4
  x += x >> 8
  return (x + (x >> 16)) & 0x3f
end

Тест: 105,73 с для 10 000 000 итераций (94 579,03 итераций в секунду)

Если вам небезразличны значения выше 2**32,

def popcount(x)
  b = 0
  while x > 0
    x &= x - 1
    b += 1
  end
  return b
end

Тест: 365,59 с для 10 000 000 итераций (27 353,27 итераций в секунду)

Addenda:

Ваш код:

Тест: 78,25 с для 1 000 000 итераций (12 779,56 итераций в секунду)

Этот код:

def bit_vec(str)
  # alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
  bv = "0" * 52
  str.each_char do |c|
    ord = c.ord
    next unless (ord >= 65 && ord <= 90) || (ord >= 97 && ord <= 122)
    index = ord - 65
    index -= 6 if index > 25
    bv[index] = "1"
    break if bv == "1111111111111111111111111111111111111111111111111111"
  end
  bv
end

Примечание: Вы сказали, что имеете дело с 52-битным числом, поэтому я предположил, что вам небезразличны как прописные, так и строчные буквы (26 + 26 = 52). Сначала я решил проверить прописные буквы, потому что так они выглядят практически в каждом наборе символов, что немного упрощает вычисления.

Тест: 24,86 с для 1 000 000 итераций (40 231,60 итераций в секунду)

3,14x ускорение.

3 голосов
/ 23 марта 2012

Вот еще один тест: https://gist.github.com/knugie/3865903

Просто запустите его на своем компьютере, если у вас есть сомнения.

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

Пример ВЫХОДА:

$ ruby bit_count_benchmark.rb
CPU       : Intel(R) Core(TM)2 Duo CPU P8400 @ 2.26GHz 
MEM       : 3083288 kB
RUBY      : ruby-1.9.2-p320

"NORM":
  TEST... OK
  BENCHMARK (2000000): 
    PREPARE... OK
    RUN...
                             user     system      total        real
scan_string            227.770000   0.250000 228.020000 (227.912435)
scan_regex             214.500000   0.220000 214.720000 (214.635405)
progressive_right_shift 43.420000   0.030000  43.450000 ( 43.412643)
continuous_right_shift  39.340000   0.010000  39.350000 ( 39.345163)
count_string            19.910000   0.030000  19.940000 ( 19.932677)
access_bit_fast         18.310000   0.040000  18.350000 ( 18.345740)
bit_elimination_for     16.400000   0.010000  16.410000 ( 16.388461)
bit_elimination_until   14.650000   0.000000  14.650000 ( 14.650187)
bit_elimination_while   14.610000   0.000000  14.610000 ( 14.604845)
pre_compute_16           4.370000   0.000000   4.370000 (  4.371228)

"NORM" FINISHED


"LOTTO":
  TEST... OK
  BENCHMARK (2000000): 
    PREPARE... OK
    RUN...
                             user     system      total        real
scan_string             92.900000   0.100000  93.000000 ( 92.947647)
scan_regex              79.500000   0.230000  79.730000 ( 79.671581)
progressive_right_shift 43.430000   0.010000  43.440000 ( 43.424880)
continuous_right_shift  35.360000   0.020000  35.380000 ( 35.360854)
count_string            19.210000   0.020000  19.230000 ( 19.215173)
access_bit_fast         17.890000   0.000000  17.890000 ( 17.890401)
bit_elimination_for      5.680000   0.010000   5.690000 (  5.680348)
bit_elimination_until    5.040000   0.010000   5.050000 (  5.054189)
bit_elimination_while    5.080000   0.020000   5.100000 (  5.093165)
pre_compute_16           4.360000   0.010000   4.370000 (  4.364988)

"LOTTO" FINISHED


DONE
3 голосов
/ 28 октября 2009

от http://www.bergek.com/2009/03/11/count-number-of-bits-in-a-ruby-integer/

yourString.scan(/1/).size

от http://snippets.dzone.com/posts/show/4233

count = 0
count += byte & 1 and byte >>= 1 until byte == 0

Вот пост с различными циклами (в с) для подсчета на основе плотности 0 против 1

http://gurmeetsingh.wordpress.com/2008/08/05/fast-bit-counting-routines/

1 голос
/ 28 октября 2009

Разделить строку на 8, найти каждую запись в таблице поиска на 128 записей и суммировать их?

Я знаю ... это смешно ... просто поделиться некоторыми идеями; -)

...