PL / SQL Сравнивая битовые строки, мне нужна операция AND? - PullRequest
0 голосов
/ 13 сентября 2011

В настоящее время у меня есть скрипт, который вычисляет коэффициент танимото по отпечаткам пальцев химической библиотеки.Однако во время тестирования я обнаружил, что моя реализация не может быть реально расширена из-за моего метода сравнения битовых строк (это занимает слишком много времени).Увидеть ниже.Это цикл, который мне нужно улучшить.Я упростил это, так что он просто смотрит на две структуры, настоящий сценарий выполняет перестановки в наборе данных структур, но это усложнит проблему, с которой я столкнулся здесь.

LOOP
-- Find the NA bit
SELECT SUBSTR(qsar_kb.fingerprint.fingerprint, var_fragment_id ,1) INTO var_na FROM qsar_kb.fingerprint where project_id = 1 AND structure_id = 1;

-- FIND the NB bit
SELECT SUBSTR(qsar_kb.fingerprint.fingerprint, var_fragment_id ,1) INTO var_nb FROM qsar_kb.fingerprint where project_id = 1 AND structure_id = 2;

-- Test for both bits the same
IF var_na > 0 AND var_nb > 0 then
var_tally := var_tally + 1;
END IF;

-- Test for bit in A on and B off
IF var_na > var_nb then
var_tna := var_tna + 1;
END IF

-- Test for bit in B on and A off.
IF var_nb > var_na then
var_tnb := var_tnb + 1;
END IF;

var_fragment_id := var_fragment_id + 1;
EXIT WHEN var_fragment_id > var_maxfragment_id;
END LOOP;

Для простого примера

Структура A = '101010'

Структура B = '011001'

В моем реальном наборе данных длина двоичного файла составляет 500 бит и более.Мне нужно знать:

1) Количество битов ВКЛ, общее для обоих

2) Количество битов ВКЛ в А, но выключено в В

3) Числобиты включены в B, но выключены в B

Так что в этом случае

1) = 1

2) = 2

3) = 2

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

MyЛогика для исправления этого будет выглядеть следующим образом:

Взять общее количество битов в обоих

A) = 3

B) = 3

Тогдавыполните операцию И и найдите, сколько бит включено в обоих = 1

Затем просто вычтите это из итоговых значений, чтобы найти количество бит в одном, но не в другом.

Так какМогу ли я выполнить операцию типа «И» над двумя строками 0 и 1, чтобы найти число общих 1?

Ответы [ 4 ]

4 голосов
/ 13 сентября 2011

Проверьте функцию BITAND .

Функция BITAND обрабатывает свои входы и выход как векторы битов;выход - побитовое И входов.

Однако, согласно документации, это работает только для 2^128

3 голосов
/ 13 сентября 2011

Вы должны переместить SELECT из цикла. Я уверен, что вы тратите 99% времени, выбирая 1 бит 500 раз, где вы можете выбрать 500 бит за один раз, а затем перебрать строку:

DECLARE
   l_structure_a LONG;
   l_structure_b LONG;
   var_na        VARCHAR2(1);
   var_nb        VARCHAR2(1);
BEGIN
   SELECT MAX(decode(structure_id, 1, fingerprint)), 
          MAX(decode(structure_id, 2, fingerprint))
     INTO l_structure_a, l_structure_b
     FROM qsar_kb.fingerprint
    WHERE project_id = 1
      AND structure_id IN (1,2);
   LOOP
      var_na := substr(l_structure_a, var_fragment_id, 1);
      var_nb := substr(l_structure_b, var_fragment_id, 1);

      -- Test for both bits the same
      IF var_na > 0 AND var_nb > 0 THEN
         var_tally := var_tally + 1;
      END IF;   
      -- Test for bit in A on and B off
      IF var_na > var_nb THEN
         var_tna := var_tna + 1;
      END IF;   
      -- Test for bit in B on and A off.
      IF var_nb > var_na THEN
         var_tnb := var_tnb + 1;
      END IF;

      var_fragment_id := var_fragment_id + 1;
      EXIT WHEN var_fragment_id > var_maxfragment_id;
   END LOOP;
END;

Edit: Вы также можете сделать это одним оператором SQL:

SQL> WITH DATA AS (
  2     SELECT '101010' fingerprint,1 project_id, 1 structure_id FROM dual
  3     UNION ALL SELECT '011001', 1, 2 FROM dual),
  4  transpose AS (SELECT ROWNUM fragment_id FROM dual CONNECT BY LEVEL <= 1000)
  5  SELECT COUNT(CASE WHEN var_na = 1 AND var_nb = 1 THEN 1 END) nb_1,
  6         COUNT(CASE WHEN var_na > var_nb THEN 1 END) nb_2,
  7         COUNT(CASE WHEN var_na < var_nb THEN 1 END) nb_3
  8    FROM (SELECT to_number(substr(struct_a, fragment_id, 1)) var_na,
  9                 to_number(substr(struct_b, fragment_id, 1)) var_nb
 10             FROM (SELECT MAX(decode(structure_id, 1, fingerprint)) struct_a,
 11                           MAX(decode(structure_id, 2, fingerprint)) struct_b
 12                      FROM DATA
 13                     WHERE project_id = 1
 14                       AND structure_id IN (1, 2))
 15            CROSS JOIN transpose);

      NB_1       NB_2       NB_3
---------- ---------- ----------
         1          2          2
0 голосов
/ 14 сентября 2011

Может быть сделано довольно просто с чем-то вроде этого:

SELECT utl_raw.BIT_AND( t.A, t.B )                                                 SET_IN_A_AND_B,
       length(replace(utl_raw.BIT_AND( t.A, t.B ), '0', ''))                       SET_IN_A_AND_B_COUNT,
       utl_raw.BIT_AND( t.A, utl_raw.bit_complement(t.B) )                         ONLY_SET_IN_A,
       length(replace(utl_raw.BIT_AND( t.A, utl_raw.bit_complement(t.B) ),'0','')) ONLY_SET_IN_A_COUNT,
       utl_raw.BIT_AND( t.B, utl_raw.bit_complement(t.A) )                         ONLY_SET_IN_A,
       length(replace(utl_raw.BIT_AND( t.B, utl_raw.bit_complement(t.A) ),'0','')) ONLY_SET_IN_A_COUNT       
  FROM (SELECT '1111100000111110101010' A, '1101011010101010100101' B FROM dual) t

Убедитесь, что ваша битовая строка имеет четную длину (просто добавьте ее к нулю, если она имеет нечетную длину).

0 голосов
/ 13 сентября 2011

Я немного расширю ответ Лукаса, добавив немного больше информации.

Немного поиска в Интернете обнаружил код от Тома Кайта (через Джонатана Льюиса) для конвертации между базами. Есть функция to_dec, которая будет принимать строку и преобразовывать ее в число. Я воспроизвел код ниже:

Преобразование базового числа в десятичное:

create or replace function to_dec( 
  p_str in varchar2, 
  p_from_base in number default 16) return number
is
    l_num   number default 0;
    l_hex   varchar2(16) default '0123456789ABCDEF';
begin
    for i in 1 .. length(p_str) loop
        l_num := l_num * p_from_base + instr(l_hex,upper(substr(p_str,i,1)))-1;
    end loop;
    return l_num;
end to_dec;

Преобразование десятичного числа в базовое число:

create or replace function to_base( p_dec in number, p_base in number ) 
return varchar2
is
    l_str   varchar2(255) default NULL;
    l_num   number  default p_dec;
    l_hex   varchar2(16) default '0123456789ABCDEF';
begin
    if ( trunc(p_dec) <> p_dec OR p_dec < 0 ) then
        raise PROGRAM_ERROR;
    end if;
    loop
        l_str := substr( l_hex, mod(l_num,p_base)+1, 1 ) || l_str;
        l_num := trunc( l_num/p_base );
        exit when ( l_num = 0 );
    end loop;
    return l_str;
end to_base;

Эта функция может быть вызвана для преобразования растрового изображения строки в число, которое затем может использоваться с bitand. Примером этого может быть:

select to_dec('101010', 2) from dual

Oracle действительно предоставляет только BITANDBIT_TO_NUM, который здесь не очень актуален) в качестве способа выполнения логических операций, но здесь требуются следующие операции (A и B), (A AND NOT B) и ( НЕ A И B). Так что нам нужна переменная для преобразования A в NOT A. Простой способ сделать это - использовать translate.

Итак ... окончательный результат:

select 
  length(translate(to_base(bitand(data_A, data_B),2),'10','1')) as nb_1,
  length(translate(to_base(bitand(data_A, data_NOT_B),2),'10','1'))  as nb_2,
  length(translate(to_base(bitand(data_NOT_A, data_B),2),'10','1'))  as nb_3
from (
  select 
    to_dec(data_A,2) as data_A, 
    to_dec(data_b,2) as data_B,
    to_dec(translate(data_A, '01', '10'),2) as data_NOT_A, 
    to_dec(translate(data_B, '01', '10'),2) as data_NOT_B
  from (
    select '101010' as data_A, '011001' as data_B from dual
  )
)

Это несколько сложнее, чем я надеялся, когда начал писать этот ответ, но, похоже, оно работает.

...