Поиск пробелов в данных с помощью битовой маскировки

Я столкнулся с проблемой поиска разрывов (разрывов) заданной длины в последовательности чисел. Так, например, учитывая [1,2,3,7,8,9,10] и пробел в length=3, я найду [4,5,6]. Если зазор будет length=4, ничего не найду. Настоящая последовательность, конечно, намного длиннее. Я видел эту проблему в нескольких сообщениях, и у нее были различные приложения и возможные реализации.

Один из способов, который, как я думал, может сработать и должен быть относительно быстрым, - это представить полный набор в виде битового массива, содержащего 1 для доступного числа и 0 для отсутствующего, поэтому приведенное выше будет выглядеть как [1,1,1,0,0,0,1,1,1,1]. Затем, возможно, запустите оконную функцию, которая будет маскировать XOR массив заданной длины с полным набором до тех пор, пока все местоположения не приведут к 1. Это потребует одного прохода по всей последовательности примерно за ~ O (n), плюс стоимость маскировка в каждом прогоне.

Вот что мне удалось придумать:

def find_gap(array, start=0, length=10):
    """
    array:  assumed to be of length MAX_NUMBER and contain 0 or 1 
            if the value is actually present
    start:  indicates what value to start looking from
    length: what the length the gap should be
    """

    # create the bitmask to check against
    mask = ''.join( [1] * length )

    # convert the input 0/1 mapping to bit string
    # e.g - [1,0,1,0] -> '1010'
    bits =''.join( [ str(val) for val in array ] )

    for i in xrange(start, len(bits) - length):

        # find where the next gap begins
        if bits[i] != '0': continue

        # gap was found, extract segment of size 'length', compare w/ mask
        if (i + length < len(bits)):
            segment = bits[i:i+length]

            # use XOR between binary masks
            result  = bin( int(mask, 2) ^ int(segment, 2) )

            # if mask == result in base 2, gap found
            if result == ("0b%s" % mask): return i

    # if we got here, no gap exists
    return -1

Это довольно быстро, ~ 100к (‹1 сек). Буду признателен за советы, как сделать это быстрее / эффективнее для больших сетов. Благодарность!


person sa125    schedule 07.12.2010    source источник
comment
начало - это массив idx, а не значение?   -  person kevpie    schedule 07.12.2010
comment
Я неправильно понял проблему. Разве нельзя просто искать соседние элементы, для которых a[i + 1] - a[i] == gap + 1?   -  person Marcelo Cantos    schedule 07.12.2010
comment
@Marcelo Я думаю, что вы действительно можете, и что OP серьезно усложняет вещи, возможно, основанный на некоторых плохо понятых идеях об оптимизации. Я написал свой ответ, исходя из этого предположения.   -  person Karl Knechtel    schedule 07.12.2010
comment
Было бы важно знать, хотите ли вы искать промежутки разной длины в одной и той же последовательности. Если вы хотите итеративно искать промежутки длиной от 1 до 100, то, возможно, стоит сначала преобразовать последовательность.   -  person Johannes Charra    schedule 07.12.2010
comment
Вы просто хотите, чтобы список, дополняющий предоставленный список, имел пробелы, соответствующие точному запрошенному размеру? [1,2,5,8] длина = 2 - ›[3,4,6,7]?   -  person kevpie    schedule 07.12.2010
comment
Я хочу первое число, с которого начинается пробел. Итак, используя мои примеры выше, вызов find_gap([1,1,1,0,0,0,1,1,1,1], 0, 3) вернет 4. Кроме того, я указал start, а не idx, чтобы разрешить поиск с произвольного числа. Извините, если я не понял.   -  person sa125    schedule 07.12.2010
comment
Я ответил на него несколькими примерами, чтобы дать вам некоторые идеи.   -  person kevpie    schedule 07.12.2010


Ответы (5)


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

def find_gaps(numbers, gap_size):
    adjacent_differences = [(y - x) for (x, y) in zip(numbers[:-1], numbers[1:])]
    # If adjacent_differences[i] > gap_size, there is a gap of that size between
    # numbers[i] and numbers[i+1]. We return all such indexes in a list - so if
    # the result is [] (empty list), there are no gaps.
    return [i for (i, x) in enumerate(adjacent_differences) if x > gap_size]

(Кроме того, пожалуйста выучите некоторые идиомы Python. Мы предпочитаем прямую итерацию, и у нас есть настоящий логический тип.)

person Karl Knechtel    schedule 07.12.2010
comment
Он не хочет различий ›= размер зазора, только == размер зазора. - person Johannes Charra; 07.12.2010
comment
Итак, ... if x == gap_size + 1 (согласно описанию, между 3 и 7 есть зазор размером 3, поэтому размер зазора на единицу меньше разницы). :) - person Karl Knechtel; 07.12.2010
comment
Хорошо, +1 (dto. За ваш ответ;)) - person Johannes Charra; 07.12.2010
comment
очень круто, хотя мне нужно немного подправить его, чтобы он работал на меня. Не уверен, что вы имеете в виду под прямой итерацией. Я знаю логический тип, просто использовал 0/1, так как в моей версии он работал лучше :) - спасибо! - person sa125; 07.12.2010

Вы можете использовать XOR и shift, и он выполняется примерно за O (n) время.

Однако на практике создание индекса (хэш-список всех пропусков, превышающих некоторую минимальную длину) может быть лучшим подходом.

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

В конце у вас есть список всех пробелов (превышающих желаемый порог) в вашей последовательности.

Одним из хороших моментов этого подхода является то, что вы должны иметь возможность поддерживать этот индекс при изменении базового списка. Таким образом, начальное время O (n * log (n)), затраченное на построение индекса, амортизируется стоимостью O (log (n)) для последующих запросов и обновлений индексов.

Вот очень грубая функция для создания gap_index():

def gap_idx(s, thresh=2):
    ret = dict()
    lw = s[0]  # initial low val.
    for z,i in enumerate(s[1:]):
        if i - lw < thresh:
            lw = i
            continue
        key = i - lw
        if key not in ret:
            ret[key] = list()
        ret[key].append(z)
        lw = i
    return ret

Класс для обслуживания набора данных и индекса лучше всего строить на основе встроенного модуля bisect и его функции insort().

person Jim Dennis    schedule 08.12.2010

Примерно то же, что и aix ... но получились только промежутки нужной длины:

def findGaps(mylist, gap_length, start_idx=0):
    gap_starts = []
    for idx in range(start_idx, len(mylist) - 1):
        if mylist[idx+1] - mylist[idx] == gap_length + 1:
            gap_starts.append(mylist[idx] + 1)

    return gap_starts

РЕДАКТИРОВАТЬ: с учетом пожеланий ОП.

person Johannes Charra    schedule 07.12.2010

Они обеспечивают один проход по вашему списку ввода.

Список значений зазора для заданной длины:

from itertools import tee, izip
def gapsofsize(iterable, length):
    a, b = tee(iterable)
    next(b, None)
    return ( p for x, y in izip(a, b) if y-x == length+1 for p in xrange(x+1,y) )

print list(gapsofsize([1,2,5,8,9], 2))

[3, 4, 6, 7]

Все значения зазора:

def gaps(iterable):
    a, b = tee(iterable)
    next(b, None)
    return ( p for x, y in izip(a, b) if y-x > 1 for p in xrange(x+1,y) )

print list(gaps([1,2,4,5,8,9,14]))

[3, 6, 7, 10, 11, 12, 13]

Список пробелов как векторов:

def gapsizes(iterable):
    a, b = tee(iterable)
    next(b, None)
    return ( (x+1, y-x-1) for x, y in izip(a, b) if y-x > 1 )

print list(gapsizes([1,2,4,5,8,9,14]))

[(3, 1), (6, 2), (10, 4)]

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

person kevpie    schedule 07.12.2010

Если вам нужна эффективность, я бы сделал что-то вроде следующих строк (где x - это список порядковых номеров):

for i in range(1, len(x)):
  if x[i] - x[i - 1] == length + 1:
    print list(range(x[i - 1] + 1, x[i]))
person NPE    schedule 07.12.2010