web-dev-qa-db-fra.com

Supprimer un élément de la liste

Je viens juste de commencer à apprendre le python et j'ai ici une liste triée de séquences de protéines (59 000 au total) et certaines d'entre elles se chevauchent. J'ai fait une liste de jouets ici par exemple:

ABCDE
ABCDEFG
ABCDEFGH
ABCDEFGHIJKLMNO
CEST
DBTSFDE
DBTSFDEO
EOEUDNBNUW
EOEUDNBNUWD
EAEUDNBNUW
FEOEUDNBNUW
FG
FGH

Je voudrais supprimer les chevauchements les plus courts et conserver le plus long afin que la sortie souhaitée ressemble à ceci:

ABCDEFGHIJKLMNO
CEST
DBTSFDEO
EAEUDNBNUW
FEOEUDNBNUWD
FGH

Comment puis-je le faire? Mon code ressemble à ceci:

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

Et j'ai eu le message d'erreur:

['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
21
Kenny

Il y a d'autres solutions de travail, mais aucune d'entre elles n'explique votre problème actuel. vous étiez en réalité très proche d’une solution valable et quelle est, à mon avis, la réponse la plus lisible.

L'erreur venait du fait que vous faisiez muter la même liste tout en vérifiant l'index à l'aide de range().

Ainsi, en augmentant la variable i, vous supprimiez un élément de la liste qui, à un moment donné, provoque inévitablement le index error

Par conséquent, voici une version de travail de votre code initial avec quelques modifications, 

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']    

Notez que ce code fonctionnera si votre liste est préalablement triée comme vous l'avez mentionné dans la section commentaire.

Que fait ce code?

Fondamentalement, il utilise la même logique que votre réponse initiale, à savoir itérer sur la liste et vérifier si l'élément suivant contient l'élément en cours. Toutefois, utiliser une autre liste et effectuer une itération jusqu'à l'élément avant le dernier élément résoudra le problème de votre index. Mais vient maintenant une question, 

Que dois-je faire avec le dernier élément? 

Puisque la liste est triée, vous pouvez considérer le dernier élément comme étant toujours unique. C'est pourquoi j'utilise 

output_pattern.append(pattern[-1])

qui ajoute le dernier élément de la liste initiale. 

Note importante

Cette réponse a été écrite en réponse à la question initiale d'OP dans laquelle il souhaitait conserver le chevauchement le plus long et je cite en fonction du point suivant de la même liste. Comme @Chris_Rands l’a déclaré, si vos préoccupations sont liées à une tâche biologique et que vous devez trouver un chevauchement, cette solution ne convient pas à vos besoins. 

Exemple où ce code ne reconnaîtrait pas un chevauchement potentiel,

pattern = ["ACD", "AD", "BACD"]

où il produirait le même résultat sans supprimer le chevauchement possible de "ACD". Maintenant, juste pour clarifier, cela impliquerait un algorithme beaucoup plus complexe et je pensais au départ que cela sortait du cadre des exigences de la question. Si c'est toujours votre cas, je me trompe peut-être complètement ici, mais je pense vraiment qu'une implémentation C++ semble plus appropriée. Jetez un coup d'œil à l'algorithme CD-Hit proposé par @Chris_Rands dans la section commentaire.

15
scharette

Vous pouvez utiliser groupby() et max() pour aider ici:

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())

Cela afficherait:

ABCDEFGHIJKLMNO
CEST
DBTSFDEO
EOEUDNBNUW
EAEUDNBNUW
FGH

groupby() fonctionne en renvoyant une liste d'éléments correspondants en fonction d'une fonction, dans ce cas des lignes consécutives contenant les mêmes 2 premiers caractères. La fonction max() prend alors cette liste et renvoie l'élément de liste le plus long.

5
Martin Evans
# 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()))

Sortie:

['ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDEO', 'EOEUDNBNUW', 'EAEUDNBNUW', 'FGH']

4
Andrej Kesely

Vous pouvez utiliser un arbre binaire dont le processus d'insertion tente de trouver les nœuds précédant la valeur:

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())

Sortie:

['ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDEO', 'EAEUDNBNUW', 'EOEUDNBNUW', 'FGH']
1
Ajax1234

Cela vous mènera où vous voulez être:

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']

J'ai ajouté set au cas où il y aurait plusieurs occurrences du même texte.

1
zipa

Code

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)

Sortie

{'ABCDEFGHIJKLMNO',
 'CEST',
 'DBTSFDEO',
 'EAEUDNBNUW',
 'EOEUDNBNUWD',
 'FEOEUDNBNUW'}

Détails

Nous utilisons read_file pour générer chaque ligne du fichier.

find_longest_sequences construit un defaultdict qui regroupe des séquences similaires. Il itère les données avec deux boucles: 

  1. La première boucle crée un dict de listes vides avec des séquences uniques en tant que clés. 
  2. La deuxième boucle ajoute comme valeurs les chaînes similaires à la clé. 

Un ensemble de valeurs est constitué du dict résultant et les séquences les plus longues sont renvoyées.

Notez quelques divergences avec votre résultat attendu:

  1. FGH chevauche ABCDEFGHIJKLMNO et n'est donc pas une sortie valide.
  2. FEOEUDNBNUWD n'est pas une séquence originale. Un post-traitement est nécessaire pour les séquences qui se chevauchent.
1
pylang

Ne correspond pas exactement à vos attentes, mais, étant donné que vous déclarez que c'est réglé (et que ce n'est pas près de EOEUDNBNUWD EAEUDNBNUW) et que je ne sais pas pourquoi vous manquez EOEUDNBNUWD Je ne suis pas sûr que vos attentes soient correctement a déclaré ou si j'ai mal interprété votre question.

(ah, oui, je vois la notion de chevauchement jette une clé dans l'approche sort et startswith).

Il serait peut-être bon que le PO répète cet aspect particulier, j'ai lu le commentaire de @DSM sans vraiment comprendre sa préoccupation. Maintenant oui.

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)

sortie:

ABCDEFGHIJKLMNO
CEST
DBTSFDEO
EAEUDNBNUW
EOEUDNBNUWD
FEOEUDNBNUW
FGH
1
JL Peyret

Une méthode simple consiste à traiter le fichier d'entrée ligne par ligne, à comparer chaque ligne avec la précédente et à conserver previous one si elle n'est pas contenue dans la ligne courante.

Le code peut être aussi simple que:

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
1
Serge Ballesta

Kenny, .__ Vous l'avez presque compris, mais @scharette a souligné deux problèmes:

  1. La boucle for et la suppression d'un élément de la liste ne doivent pas aller ensemble. Le correctif consiste à utiliser la boucle while et à augmenter explicitement l'index. La boucle while est moins efficace car elle appelle len() plusieurs fois au lieu de cela une fois, mais c'est ce qui est nécessaire pour obtenir le résultat correct.
  2. La IndexError. Cela ne se produit qu'à la toute dernière ligne. Ma façon de traiter ce problème est d’ignorer l’erreur.

Sur ce, j'ai modifié votre code pour:

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
1
Hai Vu
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)

Sortie

['ABCDEFGHIJKLMNO', 'CEST', 'DBTSFDEO', 'EOEUDNBNUW', 'EAEUDNBNUW', 'FGH']
1
Druta Ruslan

Comme indiqué dans d'autres réponses, votre erreur provient du calcul de la longueur de votre entrée au début, puis de sa mise à jour au fur et à mesure que vous réduisez la liste.

Voici une autre solution à une solution de travail:

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)))
0
jfg