Я только начал изучать питон, и вот у меня есть отсортированный список последовательностей белков (всего 59 000 последовательностей), и некоторые из них перекрываются. Вот, например, я составил список игрушек:
ABCDE
ABCDEFG
ABCDEFGH
ABCDEFGHIJKLMNO
CEST
DBTSFDE
DBTSFDEO
EOEUDNBNUW
EOEUDNBNUWD
EAEUDNBNUW
FEOEUDNBNUW
FG
FGH
Я хотел бы удалить это более короткое перекрытие и просто оставить самое длинное, чтобы желаемый результат выглядел так:
ABCDEFGHIJKLMNO
CEST
DBTSFDEO
EAEUDNBNUW
FEOEUDNBNUWD
FGH
Как мне это сделать? Мой код выглядит так:
with open('toy.txt' ,'r') as f:
pattern = f.read().splitlines()
print pattern
for i in range(0, len(pattern)):
if pattern[i] in pattern[i+1]:
pattern.remove(pattern[i])
print pattern
И я получил сообщение об ошибке:
['ABCDE', 'ABCDEFG', 'ABCDEFGH', 'ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDE', 'DBTSFDEO', 'EOEUDNBNUW', 'EAEUDNBNUW', 'FG', 'FGH']
['ABCDEFG', 'ABCDEFGH', 'ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDE', 'DBTSFDEO', 'EOEUDNBNUW', 'EAEUDNBNUW', 'FG', 'FGH']
['ABCDEFG', 'ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDE', 'DBTSFDEO', 'EOEUDNBNUW', 'EAEUDNBNUW', 'FG', 'FGH']
['ABCDEFG', 'ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDE', 'DBTSFDEO', 'EOEUDNBNUW', 'EAEUDNBNUW', 'FG', 'FGH']
['ABCDEFG', 'ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDEO', 'EOEUDNBNUW', 'EAEUDNBNUW', 'FG', 'FGH']
['ABCDEFG', 'ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDEO', 'EOEUDNBNUW', 'EAEUDNBNUW', 'FG', 'FGH']
['ABCDEFG', 'ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDEO', 'EOEUDNBNUW', 'EAEUDNBNUW', 'FG', 'FGH']
['ABCDEFG', 'ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDEO', 'EOEUDNBNUW', 'EAEUDNBNUW', 'FGH']
Traceback (most recent call last):
File "test.py", line 8, in <module>
if pattern[i] in pattern[i+1]:
IndexError: list index out of range
Всегда ли белки начинаются одинаково или они могут перекрываться в другом месте? Ваш файл отсортирован? Эти предположения могут привести к более эффективному решению, если они верны.
@Chris_Rands: Я отсортировал список раньше, поэтому я подумал сравнить элемент 1 и элемент 2, а затем элемент 2 и элемент 3 и так далее ...
Под «перекрытием» вы имеете в виду «содержит» или «начинается с»?
@DSM: Я имею в виду, что содержит. иногда последовательности "ABCD", "ABCDE", но также могут быть "EABCD"
@Kenny: ладно, это сложнее, потому что в этом случае ABCD и EABCD не будут размещены рядом друг с другом, поэтому я думаю, что некоторые ответы не сильно помогут.
@DSM Я с тобой согласен. Учитывая, что OP не упомянул об этом, хотя я думаю, что вопрос (и ответы) остается актуальным и действительно интересным. К сожалению, это не относится к его конкретной проблеме, но, скорее всего, применимо к будущим пользователям.
@scharette: да, это у меня плохо, я не понимал, что в некоторых последовательностях есть лишние буквы перед последовательностью
Для этой задачи я рекомендую CD-HIT со 100% порогом id. Эта реализация C++, вероятно, будет быстрее, чем любая реализация Python github.com/weizhongli/cdhit
Какова длина ваших последовательностей? (Очевидно, достаточно короткое, чтобы вы могли прочитать 59 КБ из них в память сразу.)
@DSM: Они различаются по длине. Моя самая короткая последовательность составляет 3 символа, а самая длинная - 142 символа
@Kenny Теперь, когда я думаю об этом. я не уверен, понимаете ли вы, что мы имеем в виду. Если бы у вас были «ABCD» и «DCBA», какой из них вы бы оставили? потому что вам нужна более короткая версия, но эти две имеют одинаковую длину? Итак, вы бы сохранили и то, и другое?
@scharette: В моих последовательностях я видел ABCD, ABCDR, ABCDRS, UEABCD, BUEABCD и т. д. (извините, мне не разрешено раскрывать фактические последовательности из-за конфиденциальности), но идея взята из приведенных выше 5 примеров, я могу сказать, что консенсусная последовательность - BUEABCDRS, потому что все примеры на 100% соответствуют консенсусу.
@Kenny Хорошо, вам понадобится более сложный алгоритм для вашего конкретного случая. Но вы все еще не отвечаете, что делать в случае, если бы у вас были «ABC» и «CBA». Какой из них предпочтительнее? Кроме того, пример, который вы использовали для пояснения проблемы, вводит в заблуждение, поскольку он все еще отсортирован и, возможно, может быть соседом в отсортированном списке.
@Kenny Я отредактировал ваш вопрос до его исходного состояния (того, за который проголосовали). В будущем, пожалуйста, будьте более конкретны, задавая вопросы. Если вам нужен ответ на вашу конкретную проблему, не стесняйтесь открывать новую ветку, но на этот раз ясную.
@scharette: спасибо. Я тоже новичок в SO, не знал правил :) Чтобы ответить на ваш вопрос, да, это очень сложная задача. Очень мало шансов увидеть ABC и CBA. в таком случае я бы предпочел сохранить оба шаблона. потому что для моего следующего шага мне нужно сопоставить все эти шаблоны с моими белковыми последовательностями и найти местоположения (в этой части у меня готов сценарий)
Можете ли вы отсортировать сами последовательности, по крайней мере, для целей общей сортировки списка? то есть "ABCD", "ABCDE", "EABCD" становится "ABCD", "ABCDE", "ABCDE"?
@Kenny Все в порядке, мы все учимся. Извините, я не смог помочь вам с вашей конкретной проблемой. Я предлагаю вам прочитать мой ответ, хотя он по-прежнему подчеркивает важную ошибку, которую вы допустили в исходном коде, и может быть полезным для вас, чтобы понять, что это такое.
Обратите внимание, FGH перекрывается с ABCDEFGHIJKLMNO. Вы уверены, что ваши ожидаемые результаты верны?
Также FEOEUDNBNUWD не является одной из ваших исходных последовательностей. Уточните, хотите ли вы пост-конкатенацию перекрывающихся последовательностей.






Здесь вы можете использовать groupby() и max():
from itertools import groupby
with open('toy.txt') as f_input:
for key, group in groupby(f_input, lambda x: x[:2]):
print(max(group, key=lambda x: len(x)).strip())
Это будет отображать:
ABCDEFGHIJKLMNO
CEST
DBTSFDEO
EOEUDNBNUW
EAEUDNBNUW
FGH
groupby() работает, возвращая список совпадающих элементов на основе функции, в данном случае последовательные строки с одинаковыми первыми двумя символами. Затем функция max() берет этот список и возвращает элемент списка с самой длинной длиной.
Они не хотят просто группировать по первым 2 символам, они хотят группировать на основе одной строки, содержащей другую.
with open('demo.txt') as f:
lines = f.readlines()
l_lines = len(lines)
n_lst = []
for i, line in enumerate(lines):
line = line.strip()
if i == l_lines - 1:
if lines[-2] not in line:
n_lst.append(line)
break
if line not in lines[i + 1]:
n_lst.append(line)
print(n_lst)
Выход
['ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDEO', 'EOEUDNBNUW', 'EAEUDNBNUW', 'FGH']
# assuming list is sorted:
pattern = ["ABCDE",
"ABCDEFG",
"ABCDEFGH",
"ABCDEFGHIJKLMNO",
"CEST",
"DBTSFDE",
"DBTSFDEO",
"EOEUDNBNUW",
"EAEUDNBNUW",
"FG",
"FGH"]
pattern = list(reversed(pattern))
def iterate_patterns():
while pattern:
i = pattern.pop()
throw_it_away = False
for p in pattern:
if p.startswith(i):
throw_it_away = True
break
if throw_it_away == False:
yield i
print(list(iterate_patterns()))
Выход:
['ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDEO', 'EOEUDNBNUW', 'EAEUDNBNUW', 'FGH']
Вы можете использовать двоичное дерево, процесс вставки которого пытается найти узлы, предшествующие значению:
class Tree:
def __init__(self, val=None):
self.left, self.value, self.right = None, val, None
def insert_val(self, _val):
if self.value is None or _val.startswith(self.value):
self.value = _val
else:
if _val < self.value:
getattr(self.left, 'insert_val', lambda x:setattr(self, 'left', Tree(x)))(_val)
else:
getattr(self.right, 'insert_val', lambda x:setattr(self, 'right', Tree(x)))(_val)
def flatten(self):
return [*getattr(self.left, 'flatten', lambda :[])(), self.value, *getattr(self.right, 'flatten', lambda :[])()]
t = Tree()
for i in open('filename.txt'):
t.insert_val(i.strip('\n'))
print(t.flatten())
Выход:
['ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDEO', 'EAEUDNBNUW', 'EOEUDNBNUW', 'FGH']
Есть и другие рабочие ответы, но ни один из них не объясняет вашу настоящую проблему. вы действительно были близки к действительному решению, и это, на мой взгляд, наиболее читаемый ответ.
Ошибка возникла из-за того, что вы были изменение того же списка при проверке индекса с помощью range().
Таким образом, увеличивая переменную i, вы удаляли элемент из списка, который в какой-то момент неизбежно вызывает index error.
Поэтому вот рабочая версия вашего исходного кода с некоторыми изменениями,
pattern = ["ABCDE","ABCDEFG","ABCDEFGH","ABCDEFGHIJKLMNO","CEST","DBTSFDE","DBTSFDEO","EOEUDNBNUW","EAEUDNBNUW","FG","FGH"]
output_pattern = []
for i in range(0, (len(pattern)-1)):
if not pattern[i] in pattern[i+1]:
output_pattern.append(pattern[i])
# Adding the last item
output_pattern.append(pattern[-1])
print (output_pattern)
>>>> ['ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDEO', 'EOEUDNBNUW', 'EAEUDNBNUW', 'FGH']
Обратите внимание, что этот код будет работать, если ваш список предварительно отсортирован, как вы упомянули в разделе комментариев.
Что делает этот код?
По сути, он использует ту же логику вашего первоначального ответа, где он выполняет итерацию по списку и проверяет, содержит ли следующий элемент текущий элемент. Но использование другого списка и повторение до элемента до прошлого решит вашу проблему с индексом. Но теперь возникает вопрос,
Что мне делать с последним предметом?
Поскольку список отсортирован, вы можете считать последний элемент всегда уникальным. Вот почему я использую
output_pattern.append(pattern[-1])
который добавляет последний элемент начального списка.
Важная заметка
Этот ответ был написан в ответ на первоначальный вопрос OP, где он хотел сохранить более длительное перекрытие, и я цитирую на основе следующего элемента в том же списке. Как заявил @Chris_Rands, если ваши проблемы связаны с биологической задачей и вам нужно найти перекрытие любой, это решение не подходит для ваших нужд.
Пример, когда этот код не смог бы распознать потенциальное перекрытие,
pattern = ["ACD", "AD", "BACD"]
где он будет выдавать тот же результат без удаления возможного перекрытия "ACD". Теперь, просто в качестве пояснения, мы с это означало бы гораздо более сложный алгоритм изначально думали, что это выходит за рамки требований вопроса. Если это когда-либо будет вашим случаем, я могу ошибаться здесь, но я действительно думаю, что реализация на C++ кажется более подходящей. взгляните на алгоритм CD-Hit, предложенный @Chris_Rands в разделе комментариев.
Это не совсем подходит, потому что, например, 'abcd' в 'babcd' -> Верно, но они разные.
@Rob Нет. Это то, чего хотел OP. Вопрос состоял в том, чтобы сохранить наибольшее перекрытие, если оно будет содержит следующим. Поэтому, учитывая "ABCD" и "BABCD", код должен сохранить первое. Вот что он делает.
Вы не можете рассматривать только i+1, например, это не подходит для pattern = ['ACD', 'AD', 'BACD']
@chris_rands Прочтите, пожалуйста, раздел комментариев к вопросу. Я обсуждал этот вопрос с OP. Его первоначальный вопрос заключался в том, чтобы обосновать свой поиск, и я цитирую на следующий пункт списка. Поэтому я так и поступил. Он продолжал изменять требования, поэтому я решил сохранить исходное состояние вопроса, за который люди проголосовали.
@scharette да, я понимаю, что это не ваша вина, вопрос изначально был неясным, но если вы понимаете биологическую проблему, для решения которой это предназначено, я не могу представить вариант использования вашего решения, и это может привести к ошибке
@Chris_Rands Я понимаю вашу озабоченность. Добавлю примечание для будущих пользователей.
@Chris_Rands Я уточнил свои вопросы в новом посте stackoverflow.com/questions/51406699/…
@scharette Пожалуйста, проверьте мою новую ветку stackoverflow.com/questions/51406699/…
Это приведет вас туда, куда вы хотите:
with open('toy.txt' ,'r') as f:
lines = f.readlines()
data = set(lines)
print(sorted([i for i in lines if len([j for j in data if j.startswith(i)])==1]))
#['ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDEO', 'EAEUDNBNUW', 'EOEUDNBNUW', 'FGH']
Я добавил set на тот случай, если один и тот же текст встречается несколько раз.
Простой способ - обрабатывать входной файл по одной строке за раз, сравнивать каждую строку с предыдущей и сохранять единицу предыдущий, если она не содержится в текущей.
Код может быть таким простым, как:
with open('toy.txt' ,'r') as f:
old = next(f).strip() # keep first line after stripping EOL
for pattern in f:
pattern = pattern.strip() # strip end of line...
if old not in pattern:
print old # keep old if it is not contained in current line
old = pattern # and store current line for next iteration
print old # do not forget last line
Как указано в других ответах, ваша ошибка возникает из-за вычисления длины вашего ввода в начале, а затем его не обновления при сокращении списка.
Вот еще один вариант рабочего решения:
with open('toy.txt', 'r') as infile:
input_lines = reversed(map(lambda s: s.strip(), infile.readlines()))
output = []
for pattern in input_lines:
if len(output) == 0 or not output[-1].startswith(pattern):
output.append(pattern)
print('\n'.join(reversed(output)))
Не совсем соответствует вашим ожиданиям, но, учитывая, что вы заявляете, что он отсортирован (а это не так, рядом с EOEUDNBNUWD EAEUDNBNUW) и что я не знаю, почему вам не хватает EOEUDNBNUWD, я не уверен, правильно ли сформулированы ваши ожидания или я неправильно прочитал ваш вопрос.
(ах, да, я вижу, что понятие перекрывать бросает вызов подходам sort и startswith).
Было бы неплохо, если бы OP повторил этот конкретный аспект, я прочитал комментарий @DSM, не особо понимая его озабоченность. Теперь я знаю.
li = sorted([i.strip() for i in """
ABCDE
ABCDEFG
ABCDEFGH
ABCDEFGHIJKLMNO
CEST
DBTSFDE
DBTSFDEO
EOEUDNBNUW
EOEUDNBNUWD
EAEUDNBNUW
FEOEUDNBNUW
FG
FGH""".splitlines() if i.strip()])
def get_iter(li):
prev = ""
for i in li:
if not i.startswith(prev):
yield(prev)
prev = i
yield prev
for v in get_iter(li):
print(v)
выход:
ABCDEFGHIJKLMNO
CEST
DBTSFDEO
EAEUDNBNUW
EOEUDNBNUWD
FEOEUDNBNUW
FGH
Код
import collections as ct
def read_file(filepath):
"""Yield a generator of lines from a file."""
with open(filepath, "r") as f:
for line in f:
yield line.strip()
def find_longest_sequences(seqs):
"""Return a dict of the long common sequences."""
seqs = tuple(seqs)
dd = ct.defaultdict(list)
[dd[k].append(seq) for seq in seqs for k in seqs if k in seq]
return {max(v, key=len) for v in dd.values()}
data = read_file("test.txt")
find_longest_sequences(data)
Выход
{'ABCDEFGHIJKLMNO',
'CEST',
'DBTSFDEO',
'EAEUDNBNUW',
'EOEUDNBNUWD',
'FEOEUDNBNUW'}
Подробности
Мы используем read_file для получения каждой строки файла.
find_longest_sequences строит defaultdict, который группирует похожие последовательности вместе. Он выполняет итерацию данных с двумя циклами:
Набор значений состоит из результирующего словаря, и возвращаются самые длинные последовательности.
Обратите внимание на некоторые расхождения с ожидаемым результатом:
FGH перекрывается с ABCDEFGHIJKLMNO и поэтому не является допустимым выходом.FEOEUDNBNUWD не является исходной последовательностью. Постобработка необходима для перекрывающихся последовательностей.Кенни, Вы почти получили это, но есть две проблемы, на которые указал @scharette:
for и удаление элемента из списка не должны идти вместе. Исправление заключается в использовании цикла while и явном увеличении индекса. Цикл while менее эффективен, потому что он вызывает len() несколько раз, а не один раз, но это то, что нужно для получения правильного результата.IndexError. Это происходит только в самой последней строке. Мой способ справиться с этой проблемой - игнорировать ошибку.При этом я изменил ваш код на:
with open('toy.txt' ,'r') as f:
pattern = f.read().splitlines()
print pattern
try:
i = 0
while i < len(pattern):
if pattern[i] in pattern[i+1]:
pattern.remove(pattern[i])
print pattern
i += 1
except IndexError:
pass
какие у тебя проблемы?