Удалить элемент из списка на основе следующего элемента в том же списке

Я только начал изучать питон, и вот у меня есть отсортированный список последовательностей белков (всего 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

какие у тебя проблемы?

Brown Bear 13.07.2018 16:49

Всегда ли белки начинаются одинаково или они могут перекрываться в другом месте? Ваш файл отсортирован? Эти предположения могут привести к более эффективному решению, если они верны.

Chris_Rands 13.07.2018 16:51

@Chris_Rands: Я отсортировал список раньше, поэтому я подумал сравнить элемент 1 и элемент 2, а затем элемент 2 и элемент 3 и так далее ...

Kenny 13.07.2018 16:54

Под «перекрытием» вы имеете в виду «содержит» или «начинается с»?

DSM 13.07.2018 17:11

@DSM: Я имею в виду, что содержит. иногда последовательности "ABCD", "ABCDE", но также могут быть "EABCD"

Kenny 13.07.2018 17:23

@Kenny: ладно, это сложнее, потому что в этом случае ABCD и EABCD не будут размещены рядом друг с другом, поэтому я думаю, что некоторые ответы не сильно помогут.

DSM 13.07.2018 17:26

@DSM Я с тобой согласен. Учитывая, что OP не упомянул об этом, хотя я думаю, что вопрос (и ответы) остается актуальным и действительно интересным. К сожалению, это не относится к его конкретной проблеме, но, скорее всего, применимо к будущим пользователям.

scharette 13.07.2018 17:33

@scharette: да, это у меня плохо, я не понимал, что в некоторых последовательностях есть лишние буквы перед последовательностью

Kenny 13.07.2018 17:35

Для этой задачи я рекомендую CD-HIT со 100% порогом id. Эта реализация C++, вероятно, будет быстрее, чем любая реализация Python github.com/weizhongli/cdhit

Chris_Rands 13.07.2018 17:38

Какова длина ваших последовательностей? (Очевидно, достаточно короткое, чтобы вы могли прочитать 59 КБ из них в память сразу.)

DSM 13.07.2018 17:39

@DSM: Они различаются по длине. Моя самая короткая последовательность составляет 3 символа, а самая длинная - 142 символа

Kenny 13.07.2018 17:51

@Kenny Теперь, когда я думаю об этом. я не уверен, понимаете ли вы, что мы имеем в виду. Если бы у вас были «ABCD» и «DCBA», какой из них вы бы оставили? потому что вам нужна более короткая версия, но эти две имеют одинаковую длину? Итак, вы бы сохранили и то, и другое?

scharette 13.07.2018 17:51

@scharette: В моих последовательностях я видел ABCD, ABCDR, ABCDRS, UEABCD, BUEABCD и т. д. (извините, мне не разрешено раскрывать фактические последовательности из-за конфиденциальности), но идея взята из приведенных выше 5 примеров, я могу сказать, что консенсусная последовательность - BUEABCDRS, потому что все примеры на 100% соответствуют консенсусу.

Kenny 13.07.2018 17:58

@Kenny Хорошо, вам понадобится более сложный алгоритм для вашего конкретного случая. Но вы все еще не отвечаете, что делать в случае, если бы у вас были «ABC» и «CBA». Какой из них предпочтительнее? Кроме того, пример, который вы использовали для пояснения проблемы, вводит в заблуждение, поскольку он все еще отсортирован и, возможно, может быть соседом в отсортированном списке.

scharette 13.07.2018 18:02

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

scharette 13.07.2018 18:09

@scharette: спасибо. Я тоже новичок в SO, не знал правил :) Чтобы ответить на ваш вопрос, да, это очень сложная задача. Очень мало шансов увидеть ABC и CBA. в таком случае я бы предпочел сохранить оба шаблона. потому что для моего следующего шага мне нужно сопоставить все эти шаблоны с моими белковыми последовательностями и найти местоположения (в этой части у меня готов сценарий)

Kenny 13.07.2018 18:17

Можете ли вы отсортировать сами последовательности, по крайней мере, для целей общей сортировки списка? то есть "ABCD", "ABCDE", "EABCD" становится "ABCD", "ABCDE", "ABCDE"?

JL Peyret 14.07.2018 02:06

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

scharette 14.07.2018 02:20

Обратите внимание, FGH перекрывается с ABCDEFGHIJKLMNO. Вы уверены, что ваши ожидаемые результаты верны?

pylang 14.07.2018 19:30

Также FEOEUDNBNUWD не является одной из ваших исходных последовательностей. Уточните, хотите ли вы пост-конкатенацию перекрывающихся последовательностей.

pylang 14.07.2018 19:36
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
20
20
2 417
11

Ответы 11

Здесь вы можете использовать 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 символам, они хотят группировать на основе одной строки, содержащей другую.

Chris_Rands 13.07.2018 17:29
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 15.07.2018 13:36

@Rob Нет. Это то, чего хотел OP. Вопрос состоял в том, чтобы сохранить наибольшее перекрытие, если оно будет содержит следующим. Поэтому, учитывая "ABCD" и "BABCD", код должен сохранить первое. Вот что он делает.

scharette 15.07.2018 13:41

Вы не можете рассматривать только i+1, например, это не подходит для pattern = ['ACD', 'AD', 'BACD']

Chris_Rands 17.07.2018 11:26

@chris_rands Прочтите, пожалуйста, раздел комментариев к вопросу. Я обсуждал этот вопрос с OP. Его первоначальный вопрос заключался в том, чтобы обосновать свой поиск, и я цитирую на следующий пункт списка. Поэтому я так и поступил. Он продолжал изменять требования, поэтому я решил сохранить исходное состояние вопроса, за который люди проголосовали.

scharette 17.07.2018 12:01

@scharette да, я понимаю, что это не ваша вина, вопрос изначально был неясным, но если вы понимаете биологическую проблему, для решения которой это предназначено, я не могу представить вариант использования вашего решения, и это может привести к ошибке

Chris_Rands 17.07.2018 12:08

@Chris_Rands Я понимаю вашу озабоченность. Добавлю примечание для будущих пользователей.

scharette 17.07.2018 14:09

@Chris_Rands Я уточнил свои вопросы в новом посте stackoverflow.com/questions/51406699/…

Kenny 18.07.2018 18:41

@scharette Пожалуйста, проверьте мою новую ветку stackoverflow.com/questions/51406699/…

Kenny 18.07.2018 18:41

Это приведет вас туда, куда вы хотите:

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, который группирует похожие последовательности вместе. Он выполняет итерацию данных с двумя циклами:

  1. Первый цикл строит словарь пустых списков с уникальными последовательностями в качестве ключей.
  2. Второй цикл добавляет в качестве значений любые строки, похожие на ключ.

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

Обратите внимание на некоторые расхождения с ожидаемым результатом:

  1. FGH перекрывается с ABCDEFGHIJKLMNO и поэтому не является допустимым выходом.
  2. FEOEUDNBNUWD не является исходной последовательностью. Постобработка необходима для перекрывающихся последовательностей.

Кенни, Вы почти получили это, но есть две проблемы, на которые указал @scharette:

  1. Цикл for и удаление элемента из списка не должны идти вместе. Исправление заключается в использовании цикла while и явном увеличении индекса. Цикл while менее эффективен, потому что он вызывает len() несколько раз, а не один раз, но это то, что нужно для получения правильного результата.
  2. 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

Другие вопросы по теме