web-dev-qa-db-fra.com

élégant trouver sous-liste dans la liste

À partir d’une liste contenant un motif connu entouré de bruit, existe-t-il un moyen élégant d’obtenir tous les éléments équivalents au motif? Voir ci-dessous pour mon code brut.

list_with_noise = [7,2,1,2,3,4,2,1,2,3,4,9,9,1,2,3,4,7,4,3,1,2,3,5]
known_pattern = [1,2,3,4]
res = []


for i in list_with_noise:
    for j in known_pattern:
        if i == j:
            res.append(i)
            continue

print res

nous aurions 2, 1, 2, 3, 4, 2, 1, 2, 3, 4, 1, 2, 3, 4, 4, 3

bonus : évite d'ajouter i si le motif complet n'est pas présent (c'est-à-dire, autoriser 1,2,3,4 mais pas 1,2,3)

exemples: 

find_sublists_in_list([7,2,1,2,3,4,2,1,2,3,4,9,9,1,2,3,4,7,4,3,1,2,3,5],[1,2,3,4])

[1,2,3,4],[1,2,3,4],[1,2,3,4]


find_sublists_in_list([7,2,1,2,3,2,1,2,3,6,9,9,1,2,3,4,7,4,3,1,2,6],[1,2,3,4])

[1,2,3],[1,2,3],[1,2,3]

Les listes contiennent des tuples nommés.

22
rikAtee

Je sais que cette question a 5 mois et qu'elle est déjà "acceptée", mais un problème très similaire sur Google m'a amené à cette question et toutes les réponses semblent avoir quelques problèmes assez importants, plus je m'ennuie et je veux tenter ma chance. à un SO réponse, alors je vais juste raconter ce que j'ai trouvé.

Si j'ai bien compris, la première partie de la question est assez triviale: il suffit de renvoyer la liste originale avec tous les éléments qui ne figurent pas dans le "modèle" filtré. Suite à cette réflexion, le premier code auquel j'ai pensé utilisait la fonction filter ():

def subfinder(mylist, pattern):
    return list(filter(lambda x: x in pattern, mylist))

Je dirais que cette solution est nettement plus succincte que la solution initiale, mais elle n’est pas plus rapide, ou du moins pas sensiblement, et j’essaie d’éviter les expressions lambda s’il n’ya pas une très bonne raison de les utiliser. En fait, la meilleure solution que je pouvais trouver impliquait une simple compréhension de liste:

def subfinder(mylist, pattern):
    pattern = set(pattern)
    return [x for x in mylist if x in pattern]

Cette solution est à la fois plus élégante et nettement plus rapide que l'originale: la compréhension est environ 120% plus rapide que l'originale, tout en insérant le motif dans un ensemble défini défonce jusqu'à 320% plus rapidement lors de mes tests.

Maintenant pour le bonus: je vais juste y entrer directement, ma solution est la suivante:

def subfinder(mylist, pattern):
    matches = []
    for i in range(len(mylist)):
        if mylist[i] == pattern[0] and mylist[i:i+len(pattern)] == pattern:
            matches.append(pattern)
    return matches

C'est une variante du "liner one inefficient" de Steven Rumbalski, qui, avec l'ajout du contrôle "mylist [i] == pattern [0]" et grâce à l'évaluation de court-circuit de python, est nettement plus rapide que l'affirmation initiale et la version d'itertools (et toutes les solutions proposées, à ma connaissance) et prend même en charge les motifs qui se chevauchent. Alors voilà.

33
mintchkin

Cela donnera la partie "bonus" de votre question:

pattern = [1, 2, 3, 4]
search_list = [7,2,1,2,3,4,2,1,2,3,4,9,9,1,2,3,4,7,4,3,1,2,3,5]
cursor = 0
found = []
for i in search_list:
    if i == pattern[cursor]:
        cursor += 1
        if cursor == len(pattern):
            found.append(pattern)
            cursor = 0
    else:
        cursor = 0

Pour les non bonus:

pattern = [1, 2, 3, 4]
search_list = [7,2,1,2,3,4,2,1,2,3,4,9,9,1,2,3,4,7,4,3,1,2,3,5]
cursor = 0
found = []
for i in search_list:
    if i != pattern[cursor]:
        if cursor > 0:
            found.append(pattern[:cursor])
        cursor = 0
    else:
        cursor += 1

Enfin, celui-ci gère les chevauchements:

def find_matches(pattern_list, search_list):
    cursor_list = []
    found = []
    for element in search_list:
        cursors_to_kill = []
        for cursor_index in range(len(cursor_list)):
            if element == pattern_list[cursor_list[cursor_index]]:
                cursor_list[cursor_index] += 1
                if cursor_list[cursor_index] == len(pattern_list):
                    found.append(pattern_list)
                    cursors_to_kill.append(cursor_index)
            else:
                cursors_to_kill.append(cursor_index)
        cursors_to_kill.reverse()
        for cursor_index in cursors_to_kill:
            cursor_list.pop(cursor_index)
        if element == pattern_list[0]:
            cursor_list.append(1)
    return found
4
Silas Ray
list_with_noise = [7,2,1,2,3,4,2,1,2,3,4,9,9,1,2,3,4,7,4,3,1,2,3,5]
string_withNoise = "".join(str(i) for i in list_with_noise)
known_pattern = [1,2,3,4]
string_pattern = "".join(str(i) for i in known_pattern)
string_withNoise.count(string_pattern)
1
user850498

Donné:

a_list = [7,2,1,2,3,4,2,1,2,3,4,9,9,1,2,3,4,7,4,3,1,2,3,5]
pat = [1,2,3,4]

Voici un one-liner inefficace:

res = [pat for i in range(len(a_list)) if a_list[i:i+len(pat)] == pat]

Voici une version plus efficace d'itertools:

from itertools import izip_longest, islice

res = []
i = 0  

while True:
    try:
        i = a_list.index(pat[0], i)
    except ValueError:
        break
    if all(a==b for (a,b) in izip_longest(pat, islice(a_list, i, i+len(pat)))):
        res.append(pat)
        i += len(pat)
    i += 1
0
Steven Rumbalski

Une solution idiomatique et composable au problème.

Premièrement, nous devons emprunter une recette itertools , consume (qui consomme et supprime un nombre donné d'éléments d'un itérateur. Ensuite, nous prenons la recette itertools pour pairwise et l'étendons à une fonction nwise à l'aide de consume:

import itertools

def nwise(iterable, size=2):
    its = itertools.tee(iterable, size)
    for i, it in enumerate(its):
        consume(it, i)  # Discards i elements from it
    return Zip(*its)

Maintenant que nous avons cela, résoudre le problème des bonus est vraiment facile:

def find_sublists_in_list(biglist, searchlist):
    searchtup = Tuple(searchlist)
    return [list(subtup) for subtup in nwise(biglist, len(searchlist)) if subtup == searchtup]

    # Or for more obscure but faster one-liner:
    return map(list, filter(Tuple(searchlist).__eq__, nwise(biglist, len(searchlist))))

De même, une solution plus succincte et plus rapide (bien qu’un peu moins jolie) au problème principal remplace:

def subfinder(mylist, pattern):
    pattern = set(pattern)
    return [x for x in mylist if x in pattern]

avec:

def subfinder(mylist, pattern):
    # Wrap filter call in list() if on Python 3 and you need a list, not a generator
    return filter(set(pattern).__contains__, mylist)

Cela se comporte de la même manière, mais évite d'avoir à stocker la variable temporaire set dans un nom et pousse tout le travail de filtrage sur C.

0
ShadowRanger