web-dev-qa-db-fra.com

Comment créer un TRIE en Python

Je suis nouveau sur Python et j'essaie d'apprendre et d'avancer. Les TRIE et les DAWG m'intéressent et j'en ai beaucoup lu, mais je ne comprends pas à quoi devrait ressembler le fichier TRIE ou DAWG en sortie.

  • Un TRIE doit-il être un objet de dictionnaires imbriqués? Où chaque lettre est divisée en lettres et ainsi de suite?
  • Une recherche effectuée sur un tel dictionnaire serait-elle rapide s'il y a 100 000 ou 500 000 entrées?
  • Comment implémenter des blocs de mots composés de plusieurs mots séparés par un espace?
  • Comment lier le préfixe ou le suffixe d'un mot à une autre partie de la structure? [pour DAWG]

Je veux comprendre le meilleur structure de sortie afin de comprendre comment en créer et en utiliser un.

J'apprécierais également ce que devrait être le sortie d'un DAWG avec TRIE.

Je ne veux pas voir de représentations graphiques avec des bulles liées les unes aux autres, je les ai vues beaucoup en lisant.

J'aimerais connaître l'objet de sortie une fois qu'un ensemble de mots est transformé en TRIE ou DAWG.

Je vous remercie. 

100
Phil

Unwind est essentiellement vrai qu'il existe de nombreuses façons différentes de mettre en œuvre un test; et, pour un grand nombre de projets évolutifs, les dictionnaires imbriqués peuvent devenir encombrants, ou du moins inefficaces. Mais comme vous venez tout juste de commencer, je pense que c'est l'approche la plus facile. vous pouvez coder une simple trie en quelques lignes. Tout d'abord, une fonction pour construire le trie:

>>> _end = '_end_'
>>> 
>>> def make_trie(*words):
...     root = dict()
...     for Word in words:
...         current_dict = root
...         for letter in Word:
...             current_dict = current_dict.setdefault(letter, {})
...         current_dict[_end] = _end
...     return root
... 
>>> make_trie('foo', 'bar', 'baz', 'barz')
{'b': {'a': {'r': {'_end_': '_end_', 'z': {'_end_': '_end_'}}, 
             'z': {'_end_': '_end_'}}}, 
 'f': {'o': {'o': {'_end_': '_end_'}}}}

Si vous n'êtes pas familier avec setdefault, il cherchera simplement une clé dans le dictionnaire (ici, letter ou _end). Si la clé est présente, elle retourne la valeur associée. sinon, il attribue une valeur par défaut à cette clé et renvoie la valeur ({} ou _end). (C'est comme une version de get qui met également à jour le dictionnaire.) 

Ensuite, une fonction pour tester si le mot est dans le trie. Cela pourrait être plus concis, mais je le laisse verbeux pour que la logique soit claire:

>>> def in_trie(trie, Word):
...     current_dict = trie
...     for letter in Word:
...         if letter in current_dict:
...             current_dict = current_dict[letter]
...         else:
...             return False
...     else:
...         if _end in current_dict:
...             return True
...         else:
...             return False
... 
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'baz')
True
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'barz')
True
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'barzz')
False
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'bart')
False
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'ba')
False

Je vous laisse l'insertion et le retrait à titre d'exercice.

Bien sûr, la suggestion de Unwind ne serait pas beaucoup plus difficile. Il pourrait y avoir un léger inconvénient de vitesse dans la recherche du sous-nœud approprié nécessitant une recherche linéaire. Mais la recherche serait limitée au nombre de caractères possibles - 27 si nous incluons _end. En outre, il n’ya rien à gagner à créer une liste massive de nœuds et à y accéder par index comme il le suggère; vous pourriez aussi bien imbriquer les listes.

Enfin, j'ajouterai que la création d'un DAWG serait un peu plus complexe car vous devez détecter les situations dans lesquelles votre mot Word actuel partage un suffixe avec un autre mot de la structure. En fait, cela peut devenir assez complexe, selon la manière dont vous souhaitez structurer le DAWG! Vous devrez peut-être apprendre des choses sur Levenshteindistance pour bien faire les choses. 

135
senderle

Regardez ça:

https://github.com/kmike/marisa-trie

Structures Trie statiques à faible consommation de mémoire pour Python (2.x et 3.x).

Les données de chaîne dans un fichier MARISA-trie peuvent nécessiter jusqu'à 50 fois moins de mémoire que dans un dict Python standard; la vitesse de recherche brute est comparable; trie fournit également des méthodes avancées rapides comme la recherche par préfixe.

Basé sur la bibliothèque marisa-trie C++.

Voici un article de blog d'une entreprise utilisant marisa trie avec succès:
https://www.repustate.com/blog/sharing-large-data-structure-across-processes-python/

Chez Repustate, la plupart de nos modèles de données que nous utilisons dans notre analyse de texte peuvent être représentés sous forme de simples paires clé-valeur, ou dictionnaires dans Python Lingo. Dans notre cas particulier, nos dictionnaires sont volumineux, quelques centaines de Mo chacun, et ils doivent être consultés en permanence. En fait, pour une requête HTTP donnée, il est possible d'accéder à 4 ou 5 modèles, chacun effectuant 20 à 30 recherches. Le problème auquel nous sommes confrontés est donc de savoir comment garder les choses rapidement pour le client aussi claires que possible pour le serveur. 

...

J'ai trouvé ce paquet, essaie marisa, qui est un wrapper Python autour d'une implémentation C++ d'un trie marisa. «Marisa» est un acronyme pour Matching Algorithm avec StorAge implémenté de manière récursive. Ce qui est génial avec les essais marisa, c’est que le mécanisme de stockage réduit considérablement la quantité de mémoire dont vous avez besoin. L'auteur du plug-in Python a déclaré une réduction de taille de 50 à 100 fois - notre expérience est similaire.

Ce qui est génial avec le paquet marisa trie, c’est que la structure de test sous-jacente peut être écrite sur le disque, puis lue via un objet mappé en mémoire. Avec une mémoire marisa trie cartographiée, toutes nos exigences sont maintenant satisfaites. La mémoire utilisée par notre serveur a considérablement diminué, d’environ 40%, et nos performances sont restées inchangées par rapport à la mise en œuvre du dictionnaire Python.

Il existe également quelques implémentations en python pur, bien que, sauf sur une plate-forme restreinte, vous souhaitiez utiliser l'implémentation sécurisée C++ décrite ci-dessus pour optimiser les performances:

23
Anentropic

Voici une liste des paquets python qui implémentent Trie:

  • marisa-trie - une implémentation basée sur C++.
  • python-trie - une implémentation simple en python pur.
  • PyTrie - une implémentation plus avancée en python pur.
  • pygtrie - une implémentation python pure de Google.
14
Tzach

Il n'y a pas de "devrait"; c'est à vous. Diverses implémentations auront des caractéristiques de performance différentes, prendront beaucoup de temps à mettre en œuvre, à comprendre et à réussir. Ceci est typique pour le développement logiciel dans son ensemble, à mon avis.

J'essaierais probablement d'abord de créer une liste globale de tous les noeuds, et de représenter les pointeurs enfants de chaque noeud sous forme de liste d'index dans la liste globale. Avoir un dictionnaire juste pour représenter le lien enfant me semble trop lourd.

13
unwind

Modifié à partir de la méthode senderle (ci-dessus). J'ai trouvé que defaultdict de Python est idéal pour créer un tri ou un arbre de préfixes.

from collections import defaultdict

class Trie:
    """
    Implement a trie with insert, search, and startsWith methods.
    """
    def __init__(self):
        self.root = defaultdict()

    # @param {string} Word
    # @return {void}
    # Inserts a Word into the trie.
    def insert(self, Word):
        current = self.root
        for letter in Word:
            current = current.setdefault(letter, {})
        current.setdefault("_end")

    # @param {string} Word
    # @return {boolean}
    # Returns if the Word is in the trie.
    def search(self, Word):
        current = self.root
        for letter in Word:
            if letter not in current:
                return False
            current = current[letter]
        if "_end" in current:
            return True
        return False

    # @param {string} prefix
    # @return {boolean}
    # Returns if there is any Word in the trie
    # that starts with the given prefix.
    def startsWith(self, prefix):
        current = self.root
        for letter in prefix:
            if letter not in current:
                return False
            current = current[letter]
        return True

# Now test the class

test = Trie()
test.insert('helloworld')
test.insert('ilikeapple')
test.insert('helloz')

print test.search('hello')
print test.startsWith('hello')
print test.search('ilikeapple')
11
dapangmao

Si vous voulez qu'un TRIE soit implémenté en tant que classe Python, voici ce que j'ai écrit après avoir lu à leur sujet:

class Trie:

    def __init__(self):
        self.__final = False
        self.__nodes = {}

    def __repr__(self):
        return 'Trie<len={}, final={}>'.format(len(self), self.__final)

    def __getstate__(self):
        return self.__final, self.__nodes

    def __setstate__(self, state):
        self.__final, self.__nodes = state

    def __len__(self):
        return len(self.__nodes)

    def __bool__(self):
        return self.__final

    def __contains__(self, array):
        try:
            return self[array]
        except KeyError:
            return False

    def __iter__(self):
        yield self
        for node in self.__nodes.values():
            yield from node

    def __getitem__(self, array):
        return self.__get(array, False)

    def create(self, array):
        self.__get(array, True).__final = True

    def read(self):
        yield from self.__read([])

    def update(self, array):
        self[array].__final = True

    def delete(self, array):
        self[array].__final = False

    def Prune(self):
        for key, value in Tuple(self.__nodes.items()):
            if not value.Prune():
                del self.__nodes[key]
        if not len(self):
            self.delete([])
        return self

    def __get(self, array, create):
        if array:
            head, *tail = array
            if create and head not in self.__nodes:
                self.__nodes[head] = Trie()
            return self.__nodes[head].__get(tail, create)
        return self

    def __read(self, name):
        if self.__final:
            yield name
        for key, value in self.__nodes.items():
            yield from value.__read(name + [key])
4
Noctis Skytower

Cette version utilise la récursivité 

import pprint
from collections import deque

pp = pprint.PrettyPrinter(indent=4)

inp = raw_input("Enter a sentence to show as trie\n")
words = inp.split(" ")
trie = {}


def trie_recursion(trie_ds, Word):
    try:
        letter = Word.popleft()
        out = trie_recursion(trie_ds.get(letter, {}), Word)
    except IndexError:
        # End of the Word
        return {}

    # Dont update if letter already present
    if not trie_ds.has_key(letter):
        trie_ds[letter] = out

    return trie_ds

for Word in words:
    # Go through each Word
    trie = trie_recursion(trie, deque(Word))

pprint.pprint(trie)

Sortie:

Coool???? <algos>????  python trie.py
Enter a sentence to show as trie
foo bar baz fun
{
  'b': {
    'a': {
      'r': {},
      'z': {}
    }
  },
  'f': {
    'o': {
      'o': {}
    },
    'u': {
      'n': {}
    }
  }
}
3
naren
from collections import defaultdict

Définir Trie:

_trie = lambda: defaultdict(_trie)

Créer un tri:

trie = _trie()
for s in ["cat", "bat", "rat", "cam"]:
    curr = trie
    for c in s:
        curr = curr[c]
    curr.setdefault("_end")

Chercher:

def Word_exist(trie, Word):
    curr = trie
    for w in Word:
        if w not in curr:
            return False
        curr = curr[w]
    return '_end' in curr

Tester:

print(Word_exist(trie, 'cam'))
1
DingLi
class Trie:
    head = {}

    def add(self,Word):

        cur = self.head
        for ch in Word:
            if ch not in cur:
                cur[ch] = {}
            cur = cur[ch]
        cur['*'] = True

    def search(self,Word):
        cur = self.head
        for ch in Word:
            if ch not in cur:
                return False
            cur = cur[ch]

        if '*' in cur:
            return True
        else:
            return False
    def printf(self):
        print (self.head)

dictionary = Trie()
dictionary.add("hi")
#dictionary.add("hello")
#dictionary.add("eye")
#dictionary.add("hey")


print(dictionary.search("hi"))
print(dictionary.search("hello"))
print(dictionary.search("hel"))
print(dictionary.search("he"))
dictionary.printf()

En dehors

True
False
False
False
{'h': {'i': {'*': True}}}
0
Mous