Python, огромная итеративная проблема выполнения

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

Функция ниже взятий список, содержащий словари, где каждый словарь содержит каждое возможное слово 20 и его местоположение от одного из этих 5 миллионов длинных слов.

Если у кого-либо есть идея, как оптимизировать это, я был бы действительно благодарен, у меня нет подсказки, как продолжить...

вот образец моего кода:

def findUnique(list):
    # Takes a list with dictionaries and compairs each element in the dictionaries
    # with the others and puts all unique element in new dictionaries and finally
    # puts the new dictionaries in a list.
    # The result is a list with (in this case) 3 dictionaries containing all unique
    # sequences and their locations from each string.
    dicList=[]
    listlength=len(list)
    s=0
    valuelist=[]
    for i in list:
        j=i.values()
        valuelist.append(j)
    while s<listlength:
        currdic=list[s]
        dic={}
        for key in currdic:
            currval=currdic[key]
            test=True
            n=0
            while n<listlength:
                if n!=s:
                    if currval in valuelist[n]: #this is where it takes to much time
                        n=listlength
                        test=False
                    else:
                        n+=1
                else:
                    n+=1
            if test:
                dic[key]=currval
        dicList.append(dic)
        s+=1
    return dicList
9
задан Autoplectic 21 December 2009 в 19:12
поделиться

4 ответа

def slices(seq, length, prefer_last=False):
  unique = {}
  if prefer_last: # this doesn't have to be a parameter, just choose one
    for start in xrange(len(seq) - length + 1):
      unique[seq[start:start+length]] = start
  else: # prefer first
    for start in xrange(len(seq) - length, -1, -1):
      unique[seq[start:start+length]] = start
  return unique

# or find all locations for each slice:
import collections
def slices(seq, length):
  unique = collections.defaultdict(list)
  for start in xrange(len(seq) - length + 1):
    unique[seq[start:start+length]].append(start)
  return unique

Эта функция (в настоящее время в моем модуле iter_util ) - O (n) (n - длина каждого слова), и вы должны использовать set (срезы ( ..)) (с заданными операциями, такими как разница), чтобы получить фрагменты, уникальные для всех слов (пример ниже). Вы также можете написать функцию для возврата набора, если вы не хотите отслеживать местоположения. Использование памяти будет высоким (хотя по-прежнему O (n), просто большой коэффициент), возможно, смягчено (хотя и не намного, если длина всего 20) с помощью специального класса «ленивого среза» , который хранит базовый последовательность (строка) плюс начало и конец (или начало и длина).

Печать уникальных фрагментов:

a = set(slices("aab", 2)) # {"aa", "ab"}
b = set(slices("abb", 2)) # {"ab", "bb"}
c = set(slices("abc", 2)) # {"ab", "bc"}
all = [a, b, c]
import operator
a_unique = reduce(operator.sub, (x for x in all if x is not a), a)
print a_unique # {"aa"}

Включая местоположения:

a = slices("aab", 2)
b = slices("abb", 2)
c = slices("abc", 2)
all = [a, b, c]
import operator
a_unique = reduce(operator.sub, (set(x) for x in all if x is not a), set(a))
# a_unique is only the keys so far
a_unique = dict((k, a[k]) for k in a_unique)
# now it's a dict of slice -> location(s)
print a_unique # {"aa": 0} or {"aa": [0]}
               # (depending on which slices function used)

В тестовом сценарии, более близком к вашим условиям, с использованием случайно сгенерированных слов из 5 миллионов символов и длина ломтика 20, использование памяти было настолько высоким, что мой тестовый сценарий быстро достиг предела основной памяти в 1 ГБ и начал перебивать виртуальную память. На тот момент Python тратил очень мало времени на процессор, и я его убил. Уменьшение либо длины фрагмента, либо длины слова (поскольку я использовал полностью случайные слова, которые сокращают количество дубликатов и увеличивают использование памяти), чтобы уместиться в основной памяти, и это длилось менее минуты. Эта ситуация плюс O (n ** 2) в исходном коде займет вечность, и именно поэтому важны алгоритмическая сложность времени и пространства.

import operator
import random
import string

def slices(seq, length):
  unique = {}
  for start in xrange(len(seq) - length, -1, -1):
    unique[seq[start:start+length]] = start
  return unique

def sample_with_repeat(population, length, choice=random.choice):
  return "".join(choice(population) for _ in xrange(length))

word_length = 5*1000*1000
words = [sample_with_repeat(string.lowercase, word_length) for _ in xrange(3)]
slice_length = 20
words_slices_sets = [set(slices(x, slice_length)) for x in words]
unique_words_slices = [reduce(operator.sub,
                              (x for x in words_slices_sets if x is not n),
                              n)
                       for n in words_slices_sets]
print [len(x) for x in unique_words_slices]
10
ответ дан 4 December 2019 в 21:10
поделиться

Позвольте мне развить ответ Роджера Пейта . Если проблема связана с памятью, я бы предложил вместо использования строк в качестве ключей к словарю вы могли бы использовать хешированное значение строки. Это позволит сэкономить на хранении дополнительной копии строк в качестве ключей (в худшем случае, в 20 раз больше, чем хранение отдельного «слова»).

import collections
def hashed_slices(seq, length, hasher=None):
  unique = collections.defaultdict(list)
  for start in xrange(len(seq) - length + 1):
    unique[hasher(seq[start:start+length])].append(start)
  return unique

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

Теперь мы можем объединить все хеши:

unique = []  # Unique words in first string

# create a dictionary of hash values -> word index -> start position
hashed_starts = [hashed_slices(word, 20, hashing_fcn) for word in words]
all_hashed = collections.defaultdict(dict)
for i, hashed in enumerate(hashed_starts) :
   for h, starts in hashed.iteritems() :
     # We only care about the first word
     if h in hashed_starts[0] :
       all_hashed[h][i]=starts

# Now check all hashes
for starts_by_word in all_hashed.itervalues() :
  if len(starts_by_word) == 1 :
    # if there's only one word for the hash, it's obviously valid
    unique.extend(words[0][i:i+20] for i in starts_by_word.values())
  else :
    # we might have a hash collision
    candidates = {}
    for word_idx, starts in starts_by_word.iteritems() :
      candidates[word_idx] = set(words[word_idx][j:j+20] for j in starts)
    # Now go that we have the candidate slices, find the unique ones
    valid = candidates[0]
    for word_idx, candidate_set in candidates.iteritems() :
      if word_idx != 0 :
        valid -= candidate_set
    unique.extend(valid)

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

Будьте осторожны, я не тестировал это. Кроме того, вы, вероятно, можете многое сделать для упрощения кода, но алгоритм имеет смысл. Сложнее всего выбрать хеш. Слишком много столкновений, и вы ничего не получите. Слишком мало, и вы столкнетесь с проблемами памяти. Если вы имеете дело только с базовыми кодами ДНК, вы можете хешировать 20-символьную строку в 40-битное число, и при этом не возникнет коллизий. Таким образом, срезы будут занимать почти четверть памяти. Это позволит сэкономить примерно 250 МБ памяти в ответе Роджера Пейта.

Код по-прежнему O (N ^ 2), но константа должна быть намного меньше.

0
ответ дан 4 December 2019 в 21:10
поделиться

Вы говорите, что у вас есть «слово» длиной в 5 миллионов символов, но мне трудно поверить, что это слово в обычном смысле.

Если вы можете предоставить дополнительную информацию о своем входных данных, может быть доступно конкретное решение.

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


edit

Я взглянул на некоторые данные генома, чтобы увидеть, как эта идея складывается, используя жестко запрограммированное отображение [acgt] -> [0123] и 4 дочерних элемента на каждый узел дерева.

  1. аденовирус 2 : 35 937 пар оснований -> 35 899 различных последовательностей из 20 оснований с использованием 469 339 тройных узлов

  2. энтеробактерий фаг лямбда : 48 502 пар оснований -> 40, 921 отдельная 20-базовая последовательность с использованием 529 384 trie-узлов.

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

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

Если вы не можете найти способ сократить ключи, вы можете попробовать использовать более компактное представление. Например, вам нужно только 2 бита для хранения [acgt] / [0123], что может сэкономить вам место за счет чуть более сложного кода.

Я не думаю, что вы можете просто перебрать это хотя - вам нужно найти способ уменьшить масштаб проблемы,

0
ответ дан 4 December 2019 в 21:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: