web-dev-qa-db-fra.com

Python: détermine le préfixe d'un ensemble de chaînes (similaires)

J'ai un ensemble de chaînes, par exemple.

my_prefix_what_ever
my_prefix_what_so_ever
my_prefix_doesnt_matter

Je veux simplement trouver la plus longue portion commune de ces chaînes, ici le préfixe. Dans ce qui précède, le résultat devrait être

my_prefix_

Les cordes

my_prefix_what_ever
my_prefix_what_so_ever
my_doesnt_matter

devrait aboutir au préfixe

my_

Existe-t-il un moyen relativement simple de déterminer le préfixe en Python (sans avoir à itérer manuellement chaque caractère)?

PS: J'utilise Python 2.6.3.

60
Kawu

Ne réécrivez jamais ce qui vous est fourni: os.path.commonprefix fait exactement ceci:

Renvoie le préfixe de chemin le plus long (pris Caractère par caractère) qui est un préfixe de tous les chemins de la liste. Si liste est vide, retourne la chaîne vide (''). Notez que cela peut retourner chemins non valides, car cela fonctionne caractère par caractère.

Pour la comparaison avec les autres réponses, voici le code:

# Return the longest prefix of all list elements.
def commonprefix(m):
    "Given a list of pathnames, returns the longest common leading component"
    if not m: return ''
    s1 = min(m)
    s2 = max(m)
    for i, c in enumerate(s1):
        if c != s2[i]:
            return s1[:i]
    return s1
111
Ned Batchelder

Ned Batchelder a probablement raison. Mais pour le plaisir, voici une version plus efficace de la réponse de phimuemue en utilisant itertools

import itertools

strings = ['my_prefix_what_ever', 
           'my_prefix_what_so_ever', 
           'my_prefix_doesnt_matter']

def all_same(x):
    return all(x[0] == y for y in x)

char_tuples = itertools.izip(*strings)
prefix_tuples = itertools.takewhile(all_same, char_tuples)
''.join(x[0] for x in prefix_tuples)

Comme un affront à la lisibilité, voici une version d'une ligne :)

>>> from itertools import takewhile, izip
>>> ''.join(c[0] for c in takewhile(lambda x: all(x[0] == y for y in x), izip(*strings)))
'my_prefix_'
13
senderle

Voici ma solution:

a = ["my_prefix_what_ever", "my_prefix_what_so_ever", "my_prefix_doesnt_matter"]

prefix_len = len(a[0])
for x in a[1 : ]:
    prefix_len = min(prefix_len, len(x))
    while not x.startswith(a[0][ : prefix_len]):
        prefix_len -= 1

prefix = a[0][ : prefix_len]
5
MRAB

Ce qui suit est une solution de travail, mais probablement assez inefficace.

a = ["my_prefix_what_ever", "my_prefix_what_so_ever", "my_prefix_doesnt_matter"]
b = Zip(*a)
c = [x[0] for x in b if x==(x[0],)*len(x)]
result = "".join(c)

Pour les petits ensembles de chaînes, ce qui précède ne pose aucun problème. Mais pour les plus grands ensembles, je codifierais personnellement une autre solution manuelle qui vérifie chaque caractère les uns après les autres et s’arrête en cas de différences. 

Algorithmiquement, cela produit la même procédure, cependant, on pourrait peut-être éviter de construire la liste c.

2
phimuemue

Juste par curiosité, j'ai trouvé un autre moyen de le faire:

def common_prefix(strings):

    if len(strings) == 1:#rule out trivial case
        return strings[0]

    prefix = strings[0]

    for string in strings[1:]:
        while string[:len(prefix)] != prefix and prefix:
            prefix = prefix[:len(prefix)-1]
        if not prefix:
            break

    return prefix

strings = ["my_prefix_what_ever","my_prefix_what_so_ever","my_prefix_doesnt_matter"]

print common_prefix(strings)
#Prints "my_prefix_"

Comme Ned l'a souligné, il est probablement préférable d'utiliser os.path.commonprefix, qui est une fonction assez élégante.

1
ThePhysicist

Voici une autre façon de faire cela en utilisant OrderedDict avec un code minimal.

import collections
import itertools

def commonprefix(instrings):
    """ Common prefix of a list of input strings using OrderedDict """

    d = collections.OrderedDict()

    for instring in instrings:
        for idx,char in enumerate(instring):
            # Make sure index is added into key
            d[(char, idx)] = d.get((char,idx), 0) + 1

    # Return prefix of keys while value == length(instrings)
    return ''.join([k[0] for k in itertools.takewhile(lambda x: d[x] == len(instrings), d)])
0
skeptichacker

Voici une solution simple et propre. L'idée est d'utiliser la fonction Zip () pour aligner tous les caractères en les plaçant dans une liste de 1ers caractères, liste de 2èmes caractères, ... liste de nième caractères. Ensuite, parcourez chaque liste pour vérifier si elles ne contiennent qu'une seule valeur.

a = ["my_prefix_what_ever", "my_prefix_what_so_ever", "my_prefix_doesnt_matter"]

list = [all(x[i] == x[i+1] for i in range(len(x)-1)) for x in Zip(*a)]

print a[0][:list.index(0) if list.count(0) > 0 else len(list)]

sortie: mon_prefix_

0
Patmanizer