Определить, является ли последовательность кратной последовательности в Python - программирование
Подтвердить что ты не робот

Определить, является ли последовательность кратной последовательности в Python

У меня есть кортеж нулей и единиц, например:

(1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1)

Оказывается:

(1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1) == (1, 0, 1, 1) * 3

Мне нужна функция f такая, что если s является непустым набором нулей и единиц, f(s) является самым коротким субэлементом r таким, что s == r * n для некоторого положительного целого n.

Так, например,

f( (1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1) ) == (1, 0, 1, 1)

Что такое гладкий способ записи функции f в Python?

Edit:

Наивный метод, который я использую в настоящее время

def f(s):
  for i in range(1,len(s)):
    if len(s)%i == 0 and s == s[:i] * (len(s)/i):
      return s[:i]
4b9b3361

Ответ 1

Я считаю, что у меня есть решение времени O (n) (на самом деле 2n + r, n - длина кортежа, r - sub tuplle), который не использует деревья суффиксов, но использует алгоритм сопоставления строк (например, KMP, который вы должен быть найден на полке).

Воспользуемся следующей малоизвестной теоремой:

If x,y are strings over some alphabet,

then xy = yx if and only if x = z^k and y = z^l for some string z and integers k,l.

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

Чтобы определить, является ли строка циклическим сдвигом самого себя, мы объединяем ее с собой (она даже не должна быть реальной concat, просто виртуальной будет делать) и проверить соответствие подстроки (с самим собой).

Для доказательства этого предположим, что строка является циклическим сдвигом.

Мы имеем, что данная строка y = uv = vu. Так как uv = vu, мы должны иметь, что u = z ^ k и v = z ^ l и, следовательно, y = z ^ {k + l} из предыдущей теоремы. Другое направление легко доказать.

Вот код python. Этот метод называется powercheck.

def powercheck(lst):
    count = 0
    position = 0
    for pos in KnuthMorrisPratt(double(lst), lst):
        count += 1
        position = pos
        if count == 2:
            break

    return lst[:position]


def double(lst):
    for i in range(1,3):
        for elem in lst:
            yield elem

def main():
    print powercheck([1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1])

if __name__ == "__main__":
    main()

И вот код KMP, который я использовал (из-за Дэвида Эпштейна).

# Knuth-Morris-Pratt string matching
# David Eppstein, UC Irvine, 1 Mar 2002

def KnuthMorrisPratt(text, pattern):

    '''Yields all starting positions of copies of the pattern in the text.
Calling conventions are similar to string.find, but its arguments can be
lists or iterators, not just strings, it returns all matches, not just
the first one, and it does not need the whole text in memory at once.
Whenever it yields, it will have read the text exactly up to and including
the match that caused the yield.'''

    # allow indexing into pattern and protect against change during yield
    pattern = list(pattern)

    # build table of shift amounts
    shifts = [1] * (len(pattern) + 1)
    shift = 1
    for pos in range(len(pattern)):
        while shift <= pos and pattern[pos] != pattern[pos-shift]:
            shift += shifts[pos-shift]
        shifts[pos+1] = shift

    # do the actual search
    startPos = 0
    matchLen = 0
    for c in text:
        while matchLen == len(pattern) or \
              matchLen >= 0 and pattern[matchLen] != c:
            startPos += shifts[matchLen]
            matchLen -= shifts[matchLen]
        matchLen += 1
        if matchLen == len(pattern):
            yield startPos

Для вашего образца эти выходы

[1,0,1,1]

как ожидалось.

Я сравнивал это с кодом shx2 (а не с numpy one), создавая случайную 50-битную строку, затем репликацию, чтобы сделать общую длину равной 1 миллиону. Это был выход (десятичное число - это выход time.time())

1362988461.75

(50, [1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1])

1362988465.96

50 [1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1]

1362988487.14

Вышеуказанный метод занял ~ 4 секунды, а метод shx2 занял ~ 21 секунду!

Вот код времени. (метод shx2 назывался powercheck2).

def rand_bitstring(n):
    rand = random.SystemRandom()
    lst = []
    for j in range(1, n+1):
        r = rand.randint(1,2)
        if r == 2:
            lst.append(0)
        else:
            lst.append(1)

    return lst

def main():
    lst = rand_bitstring(50)*200000
    print time.time()
    print powercheck(lst)
    print time.time()
    powercheck2(lst)
    print time.time()

Ответ 2

Следующее решение - O (N ^ 2), но имеет то преимущество, что не создает никаких копий (или фрагментов) ваших данных, поскольку оно основано на итераторах.

В зависимости от размера вашего ввода факт, что вы избегаете делать копии данных, может привести к значительному ускорению, но, конечно же, он не будет масштабироваться также и для огромных ресурсов как алгоритмы с меньшей сложностью (например, O (N * LogN)).

[Это вторая ревизия моего решения, первая из которых приведена ниже. Это проще понять и больше по линиям умножения OP, только с использованием итераторов.]

from itertools import izip, chain, tee

def iter_eq(seq1, seq2):
    """ assumes the sequences have the same len """
    return all( v1 == v2 for v1, v2 in izip(seq1, seq2) )

def dup_seq(seq, n):
    """ returns an iterator which is seq chained to itself n times """
    return chain(*tee(seq, n))

def is_reps(arr, slice_size):
    if len(arr) % slice_size != 0:
        return False
    num_slices = len(arr) / slice_size
    return iter_eq(arr, dup_seq(arr[:slice_size], num_slices))

s = (1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1)
for i in range(1,len(s)):
    if is_reps(s, i):
        print i, s[:i]
        break

[Мое оригинальное решение]

from itertools import islice

def is_reps(arr, num_slices):
    if len(arr) % num_slices != 0:
        return False
    slice_size = len(arr) / num_slices
    for i in xrange(slice_size):
        if len(set( islice(arr, i, None, num_slices) )) > 1:
            return False
    return True

s = (1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1)
for i in range(1,len(s)):
    if is_reps(s, i):
        print i, s[:i]
        break

Вы можете избежать вызова set(), используя что-то вроде:

def is_iter_unique(seq):
    """ a faster version of testing len(set(seq)) <= 1 """
    seen = set()
    for x in seq:
        seen.add(x)
        if len(seen) > 1:
            return False
    return True

и заменив эту строку:

if len(set( islice(arr, i, None, num_slices) )) > 1:

с:

if not is_iter_unique(islice(arr, i, None, num_slices)):

Ответ 3

Упрощение решения Knoothe. Его алгоритм прав, но его реализация слишком сложна. Эта реализация также O (n).

Поскольку ваш массив состоит только из единиц и нулей, то я использую существующую реализацию str.find(Bayer Moore) для реализации идеи Knoothe. Это невероятно проще и удивительно быстрее во время выполнения.

def f(s):
    s2 = ''.join(map(str, s))
    return s[:(s2+s2).index(s2, 1)]

Ответ 4

Здесь другое решение (конкурирующее с моим более ранним решением на основе итераторов), используя numpy.

Он делает (одну) копию ваших данных, но, пользуясь тем фактом, что ваши значения равны 0 и 1, это очень быстро, благодаря numpy magics.

import numpy as np

def is_reps(arr, slice_size):
    if len(arr) % slice_size != 0:
        return False
    arr = arr.reshape((-1, slice_size))
    return (arr.all(axis=0) | (~arr).all(axis=0)).all()

s = (1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1) * 1000
a = np.array(s, dtype=bool)
for i in range(1,len(s)):
    if is_reps(a, i):
        print i, s[:i]
        break

Ответ 5

Просто другой подход к проблеме

Сначала я определяю все факторы длины, а затем разбиваю список и проверяю, одинаковы ли все части

>>> def f(s):
    def factors(n):
        #http://stackoverflow.com/a/6800214/977038
        return set(reduce(list.__add__,
                ([i, n//i] for i in range(2, int(n**0.5) + 1) if n % i == 0)))
    _len = len(s)
    for fact in reversed(list(factors(_len))):
        compare_set = set(izip(*[iter(s)]*fact))
        if len(compare_set) == 1:
            return compare_set


>>> f(t)
set([(1, 0, 1, 1)])

Ответ 6

Вы можете заархивировать его в сублинейном времени, выполнив XOR'ившуюся двоичную форму для входного массива:

  • получить двоичное представление массива, input_binary
  • из i = 1 to len(input_array)/2, и для каждого цикла поверните input_binary вправо на i бит, сохраните его как rotated_bin, затем сравните XOR с rotated_bin и input_binary.
  • Первый i, который дает 0, является индексом, которым является желаемая подстрока.

Полный код:

def get_substring(arr):
    binary = ''.join(map(str, arr)) # join the elements to get the binary form

    for i in xrange(1, len(arr) / 2):
        # do a i bit rotation shift, get bit string sub_bin
        rotated_bin = binary[-i:] + binary[:-i]
        if int(rotated_bin) ^ int(binary) == 0:
            return arr[0:i]

    return None


if __name__ == "__main__":
    test = [1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1]
    print get_substring(test) # [1,0,1,1]

Ответ 7

Это просто нечеткое рекурсивное сравнение в Haskell. Для Knoothe миллион длинной строки (f a) требуется около одной секунды. Крутая проблема! Я подумаю об этом еще.

a = concat $ replicate 20000 
    [1,1,1,0,0,1,0,1,0,0,1,0,0,1,1,1,0,0,
     0,0,0,0,1,1,1,1,0,0,0,1,1,0,1,1,1,1,
     1,1,1,0,0,1,1,1,0,0,0,0,0,1]

f s = 
  f' s [] where
    f' [] result = []
    f' (x:xs) result =
      let y = result ++ [x]   
      in if concat (replicate (div (length s) (length y)) y) == s
            then y
            else f' xs y