Удалить элемент из списка на основе следующего элемента в том же списке
Я только начал изучать питон, и здесь у меня есть отсортированный список белковых последовательностей (всего 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
Ответы
Ответ 1
Существуют и другие рабочие ответы, но ни одна из них не объясняет вашу актуальную проблему. вы на самом деле действительно близки к правильному решению и, на мой взгляд, наиболее читаемый ответ.
Ошибка возникла из-за того, что вы изменяли один и тот же список при проверке индекса с помощью 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 в разделе комментариев.
Ответ 2
Вы можете использовать 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()
берет этот список и возвращает элемент списка с самой длинной длиной.
Ответ 3
# 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']
Ответ 4
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']
Ответ 5
Вы можете использовать двоичное дерево, процесс вставки которого пытается найти узлы, которые предшествуют значению:
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']
Ответ 6
Это позволит вам, где вы хотите быть:
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
только в случае нескольких вхождений одного и того же текста.
Ответ 7
Простым способом является обработка входного файла по одной строке за раз, сравните каждую строку с предыдущей и сохраните предыдущую, если она не содержится в текущей.
Код может быть простым:
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
Ответ 8
Не точное соответствие вашим ожиданиям, но, учитывая, что вы заявляете, что он отсортирован (и это не так, около 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
Ответ 9
Код
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, который группирует аналогичные последовательности вместе. Он выполняет итерацию данных с помощью двух циклов:
- В первом цикле строит список пустых списков с уникальными последовательностями в виде ключей.
- Второй цикл добавляет в качестве значений любые строки, похожие на ключ.
Набор значений производится из полученного dict, и возвращаются самые длинные последовательности.
Обратите внимание на некоторые несоответствия с ожидаемым результатом:
-
FGH
перекрывается с ABCDEFGHIJKLMNO
и, следовательно, не является допустимым выходом. -
FEOEUDNBNUWD
не является исходной последовательностью. Пост-обработка необходима для перекрывающихся последовательностей.
Ответ 10
Кенни, ты почти понял это, но есть две проблемы, которые @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
Ответ 11
Как указано в других ответах, ваша ошибка возникает из расчета длины вашего ввода в начале и затем не обновляя его при сокращении списка.
Вот еще один пример:
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)))