web-dev-qa-db-fra.com

La plus longue chaîne de mots d'une liste de mots

Donc, cela fait partie d'une fonction que j'essaie de créer.

Je ne veux pas que le code soit trop compliqué.

J'ai une liste de mots, par exemple.

words = ['giraffe', 'elephant', 'ant', 'tiger', 'racoon', 'cat', 'hedgehog', 'mouse']

L'idée de la séquence de chaînes de mots est que le prochain mot commence par la lettre dans laquelle le dernier mot s'est terminé.

(Edit: chaque mot ne peut pas être utilisé plus d'une fois. À part cela, il n'y a pas d'autres contraintes.)

Je veux que la sortie donne la plus longue séquence de chaînes Word, qui dans ce cas est:

['hedgehog', 'giraffe', 'elephant', 'tiger', 'racoon']

Je ne sais pas trop comment faire, j'ai eu plusieurs tentatives pour essayer cela. L'un d'eux...

Ce code trouve correctement la chaîne Word si nous commençons par un mot spécifique de la liste, par exemple. mots [0] (donc 'girafe'):

words = ['giraffe', 'elephant', 'ant', 'tiger', 'racoon', 'cat', 'hedgehog', 'mouse']

Word_chain = []

Word_chain.append(words[0])

for Word in words:
    for char in Word[0]:

       if char == Word_chain[-1][-1]:
            Word_chain.append(Word)

print(Word_chain)

Sortie:

['giraffe', 'elephant', 'tiger', 'racoon']

MAIS, je veux trouver la chaîne de mots la plus longue possible (expliquée ci-dessus).

Ma méthode: Alors, j'ai essayé d'utiliser le code de travail ci-dessus que j'ai écrit et parcouru, en utilisant chaque mot de la liste comme point de départ et en trouvant la chaîne de mots pour chaque mot [0], Word [1], Word [2] etc. J'ai ensuite essayé de trouver la chaîne de mots la plus longue en utilisant une instruction if et de comparer la longueur à la chaîne la plus longue précédente, mais je ne peux pas le faire correctement et je ne sais pas vraiment. où cela va.

words = ['giraffe', 'elephant', 'ant', 'tiger', 'racoon', 'cat', 'hedgehog', 'mouse']

Word_chain = []
max_length = 0
for starting_Word_index in range(len(words) - 1):

    Word_chain.append(words[starting_Word_index])

    for Word in words:
        for char in Word[0]:

            if char == Word_chain[-1][-1]:
                Word_chain.append(Word)

    # Not sure

    if len(Word_chain) > max_length:
        final_Word_chain = Word_chain
        longest = len(Word_chain)
        Word_chain.clear()

print(final_Word_chain)

C’est ma nième tentative, je pense que celle-ci imprime une liste vide, j’avais eu auparavant différentes tentatives qui n’avaient pas réussi à effacer correctement la liste Word_chain et qui finissaient par répéter des mots.

Toute aide très appréciée. Espérons que je n'ai pas fait cela trop fastidieux ou déroutant ... Merci!

37
Mandingo

Vous pouvez utiliser la récursivité pour explorer chaque "branche" qui apparaît lorsque chaque lettre possible contenant le caractère initial correct est ajoutée à une liste en cours d'exécution:

words = ['giraffe', 'elephant', 'ant', 'tiger', 'racoon', 'cat', 'hedgehog', 'mouse']
def get_results(_start, _current, _seen):
  if all(c in _seen for c in words if c[0] == _start[-1]):
    yield _current
  else:
      for i in words:
        if i[0] == _start[-1]:
          yield from get_results(i, _current+[i], _seen+[i])


new_d = [list(get_results(i, [i], []))[0] for i in words]
final_d = max([i for i in new_d if len(i) == len(set(i))], key=len)

Sortie:

['hedgehog', 'giraffe', 'elephant', 'tiger', 'racoon']

Cette solution fonctionne de manière similaire à la recherche en largeur d'abord, car la fonction get_resuls continuera à parcourir la liste entière tant que la valeur actuelle n’a pas été appelée auparavant. Les valeurs observées par la fonction sont ajoutées au _seen liste, finissant par mettre fin au flux d'appels récursifs.

Cette solution ignorera également les résultats contenant des doublons:

words = ['giraffe', 'elephant', 'ant', 'ning', 'tiger', 'racoon', 'cat', 'hedgehog', 'mouse',]
new_d = [list(get_results(i, [i], []))[0] for i in words]
final_d = max([i for i in new_d if len(i) == len(set(i))], key=len)

Sortie:

['ant', 'tiger', 'racoon', 'ning', 'giraffe', 'elephant']
27
Ajax1234

J'ai une nouvelle idée, comme le montre la figure:

enter image description here

Nous pouvons construire un graphe orienté par Word [0] == Word [-1], puis le problème est converti pour trouver le chemin de longueur maximale.

16
TimeSeam

Comme mentionné par d'autres, le problème est de trouver le le plus long chemin d'un graphe acyclique dirigé .

Pour tout ce qui concerne les graphes liés en Python, networkx est votre ami.

Vous devez simplement initialiser le graphique, ajouter les nœuds, ajouter les arêtes et lancer dag_longest_path :

import networkx as nx
import matplotlib.pyplot as plt

words = ['giraffe', 'elephant', 'ant', 'tiger', 'racoon', 'cat',
         'hedgehog', 'mouse']

G = nx.DiGraph()
G.add_nodes_from(words)

for Word1 in words:
    for Word2 in words:
        if Word1 != Word2 and Word1[-1] == Word2[0]:
            G.add_Edge(Word1, Word2)
nx.draw_networkx(G)
plt.show()
print(nx.algorithms.dag.dag_longest_path(G))

enter image description here

Il produit:

['hedgehog', 'giraffe', 'elephant', 'tiger', 'racoon']

Remarque: cet algorithme ne fonctionne que s'il n'y a pas de cycles (boucles) dans le graphique. Cela signifie que ça va échouer avec ['ab', 'ba'] car il y aurait un chemin de longueur infinie: ['ab', 'ba', 'ab', 'ba', 'ab', 'ba', ...]

11
Eric Duminil

Dans l’esprit des solutions de force brute, vous pouvez vérifier toutes les permutations de la liste words et choisir la meilleure séquence de démarrage en continu:

from itertools import permutations

def continuous_starting_sequence(words):
    chain = [words[0]]
    for i in range(1, len(words)):
        if not words[i].startswith(words[i - 1][-1]):
            break
        chain.append(words[i])
    return chain

words = ['giraffe', 'elephant', 'ant', 'tiger', 'racoon', 'cat', 'hedgehog', 'mouse']
best = max((continuous_starting_sequence(seq) for seq in permutations(words)), key=len)

print(best)
# ['hedgehog', 'giraffe', 'elephant', 'tiger', 'racoon']

Puisque nous examinons toutes les permutations, nous savons qu'il doit y avoir une permutation qui commence par la plus grande chaîne de mots.

Ceci, bien sûr, a O (n n!) complexité temporelle: D

4
slider

Cette fonction crée un type d'itérateur appelé générateur (voir: Que fait le mot-clé "rendement"? ). Il crée de manière récursive d'autres instances du même générateur pour explorer toutes les séquences de queue possibles:

words = ['giraffe', 'elephant', 'ant', 'tiger', 'racoon', 'cat', 'hedgehog', 'mouse']

def chains(words, previous_Word=None):
    # Consider an empty sequence to be valid (as a "tail" or on its own):
    yield []
    # Remove the previous Word, if any, from consideration, both here and in any subcalls:
    words = [Word for Word in words if Word != previous_Word]
    # Take each remaining Word...
    for each_Word in words:
        # ...provided it obeys the chaining rule
        if not previous_Word or each_Word.startswith(previous_Word[-1]):
            # and recurse to consider all possible tail sequences that can follow this particular Word:
            for tail in chains(words, previous_Word=each_Word):
                # Concatenate the Word we're considering with each possible tail:
                yield [each_Word] + tail  

all_legal_sequences = list(chains(words))  # convert the output (an iterator) to a list
all_legal_sequences.sort(key=len) # sort the list of chains in increasing order of chain length
for seq in all_legal_sequences: print(seq)
# The last line (and hence longest chain) prints as follows:
# ['hedgehog', 'giraffe', 'elephant', 'tiger', 'racoon']

Ou, pour aller directement à la plus longue chaîne plus efficacement:

print(max(chains(words), key=len)

Enfin, voici une version alternative qui autorise la répétition de mots (par exemple, si vous incluez un mot N fois, vous pouvez l’utiliser jusqu’à N fois dans la chaîne):

def chains(words, previous_Word_index=None):
    yield []
    if previous_Word_index is not None:
        previous_letter = words[previous_Word_index][-1]
        words = words[:previous_Word_index] + words[previous_Word_index + 1:]
    for i, each_Word in enumerate( words ):
        if previous_Word_index is None or each_Word.startswith(previous_letter):
            for tail in chains(words, previous_Word_index=i):
                yield [each_Word] + tail  
3
jez

J'ai une approche arborescente pour cette question qui pourrait être plus rapide. Je travaille toujours sur la mise en œuvre du code mais voici ce que je ferais:

    1. Form a tree with the root node as first Word. 
    2. Form the branches if there is any Word or words that starts 
with the alphabet with which this current Word ends.
    3. Exhaust the entire given list based on the ending alphabet
 of current Word and form the entire tree.
    4. Now just find the longest path of this tree and store it.
    5. Repeat steps 1 to 4 for each of the words given in the list
 and print the longest path among the longest paths we got above.

J'espère que cela pourrait donner une meilleure solution au cas où une longue liste de mots serait donnée. Je mettrai à jour ceci avec l'implémentation réelle du code.

2
CodeHunter

Voici une approche de force brute récursive qui fonctionne:

def brute_force(pool, last=None, so_far=None):
    so_far = so_far or []
    if not pool:
        return so_far
    candidates = []
    for w in pool:
        if not last or w.startswith(last):
            c_so_far, c_pool = list(so_far) + [w], set(pool) - set([w])
            candidates.append(brute_force(c_pool, w[-1], c_so_far))
    return max(candidates, key=len, default=so_far)

>>> brute_force(words)
['hedgehog', 'giraffe', 'elephant', 'tiger', 'racoon']

À chaque appel récursif, ceci essaie de continuer la chaîne avec chaque mot éligible du pool restant. Il choisit ensuite la plus longue de ces prolongations.

2
schwobaseggl

Une autre réponse utilisant une approche récursive:

def Word_list(w_list, remaining_list):
    max_result_len=0
    res = w_list
    for Word_index in range(len(remaining_list)):
        # if the last letter of the Word list is equal to the first letter of the Word
        if w_list[-1][-1] == remaining_list[Word_index][0]:
            # make copies of the lists to not alter it in the caller function
            w_list_copy = w_list.copy()
            remaining_list_copy = remaining_list.copy()
            # removes the used Word from the remaining list
            remaining_list_copy.pop(Word_index)
            # append the matching Word to the new Word list
            w_list_copy.append(remaining_list[Word_index])
            res_aux = Word_list(w_list_copy, remaining_list_copy)
            # Keep only the longest list
            res = res_aux if len(res_aux) > max_result_len else res 
    return res

words = ['giraffe', 'elephant', 'ant', 'tiger', 'racoon', 'cat', 'hedgehog', 'mouse']
Word_list(['dog'], words)

sortie:

['dog', 'giraffe', 'elephant', 'tiger', 'racoon']
1
Pedro Torres

Espérons une manière plus intuitive de le faire sans récursivité. Parcourez la liste et laissez le type et la compréhension de la liste de Python se charger du travail:

words = ['giraffe', 'elephant', 'ant', 'tiger', 'racoon', 'cat', 'hedgehog', 'mouse']

def chain_longest(pivot, words):
    new_words = []
    new_words.append(pivot)
    for Word in words:
        potential_words = [i for i in words if i.startswith(pivot[-1]) and i not in new_words]
        if potential_words:
            next_Word = sorted(potential_words, key = lambda x: len)[0]
            new_words.append(next_Word)
            pivot = next_Word
        else:
            pass
    return new_words

max([chain_longest(i, words) for i in words], key = len)
>>
['hedgehog', 'giraffe', 'elephant', 'tiger', 'racoon']

Définir un pivot et vérifier pour potential_words si elles commencent par votre mot pivot et n'apparaissent pas dans votre nouvelle liste de mots. Si trouvé, il suffit de les trier par longueur et de prendre le premier élément.

La compréhension de la liste parcourt chaque mot comme un pivot et vous renvoie la chaîne la plus longue.

1
BernardL