web-dev-qa-db-fra.com

Recherche d'anagrammes pour un mot donné

Deux mots sont des anagrammes si l’un d’eux a exactement les mêmes caractères que celui d’un autre mot.

Exemple: Anagram & Nagaram sont des anagrammes (insensible à la casse).

Maintenant, il y a beaucoup de questions similaires à celle-ci. Quelques approches pour déterminer si deux chaînes sont des anagrammes sont les suivantes:

1) Sort les chaînes et les comparer.

2) Créez un frequency map pour ces chaînes et vérifiez si elles sont identiques ou non.

Mais dans ce cas, on nous donne un mot (par souci de simplicité, supposons un mot unique et il n’aura que des anagrammes de mots uniques) et nous devons trouver des anagrammes pour cela. 

La solution à laquelle je pense est que nous pouvons générer toutes les permutations pour le mot et vérifier lequel de ces mots existe dans le dictionnaire . Mais clairement, c'est très inefficace. Oui, le dictionnaire est également disponible.

Alors, quelles alternatives avons-nous ici?

J'ai également lu dans un fil de discussion similaire que quelque chose peut être fait en utilisant Tries mais la personne n'a pas expliqué en quoi consistait l'algorithme et pourquoi avons-nous utilisé un Trie en premier lieu; Donc, ce n'était pas vraiment utile, c'est pourquoi j'ai créé ce nouveau fil. Si quelqu'un veut partager son implémentation (autre que C, C++ ou Java), merci de l'expliquer.

36
h4ck3d

Exemple d'algorithme:

Open dictionary
Create empty hashmap H
For each Word in dictionary:
  Create a key that is the Word's letters sorted alphabetically (and forced to one case)
  Add the Word to the list of words accessed by the hash key in H

Pour vérifier toutes les anagrammes d'un mot donné:

Create a key that is the letters of the Word, sorted (and forced to one case)
Look up that key in H
You now have a list of all anagrams

Relativement rapide à construire, extrêmement rapide en recherche.

70
Vatine

J'ai imaginé une nouvelle solution. Il utilise le théorème fondamental de l'arithmétique. L'idée est donc d'utiliser un tableau des 26 premiers nombres premiers. Ensuite, pour chaque lettre du mot d'entrée, nous obtenons le nombre premier correspondant A = 2, B = 3, C = 5, D = 7… et nous calculons ensuite le produit de notre mot d'entrée. Ensuite, nous le faisons pour chaque mot du dictionnaire et si un mot correspond à notre mot d’entrée, nous l’ajoutons à la liste résultante. Tous les anagrammes auront la même signature car 

Tout nombre entier supérieur à 1 est un nombre premier ou peut être écrit comme un produit unique de nombres premiers (en ignorant la commande).

Voici le code. Je convertis le mot en MAJUSCULES et 65 correspond à la position de A qui correspond à mon premier nombre premier:

private int[] PRIMES = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31,
        37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103,
        107, 109, 113 };

C'est la méthode:

 private long calculateProduct(char[] letters) {
    long result = 1L;
    for (char c : letters) {
        if (c < 65) {
            return -1;
        }
        int pos = c - 65;
        result *= PRIMES[pos];
    }
    return result;
}
16
ACV

Nous savons que si deux mots n'ont pas la même longueur, ils ne sont pas des anagrammes. Vous pouvez donc partitionner votre dictionnaire en groupes de mots de même longueur.

Nous nous concentrons maintenant sur un seul de ces groupes et, fondamentalement, tous les mots ont exactement la même longueur dans cet univers plus petit.

Si chaque position de la lettre est une dimension et que la valeur de cette dimension est basée sur la lettre (dites le code ASCII). Ensuite, vous pouvez calculer la longueur du vecteur Word.

Par exemple, dites 'A' = 65, 'B' = 66, puis length("AB") = sqrt(65*65 + 66*66). De toute évidence, length("AB") = length("BA").

Clairement, si deux mots sont des anagrammes, leurs vecteurs ont la même longueur. La question suivante est la suivante: si deux vecteurs Word (de même nombre de lettres) ont la même longueur, sont-ils des anagrammes? Intuitivement, je dirais non, car tous les vecteurs de cette longueur forment une sphère, ils sont nombreux. Pas sûr, puisque nous sommes dans l'espace entier dans ce cas, combien y en a-t-il réellement.

Mais à tout le moins, cela vous permet de partitionner votre dictionnaire encore plus loin. Pour chaque mot de votre dictionnaire, calcule la distance du vecteur: for(each letter c) { distance += c*c }; distance = sqrt(distance);

Créez ensuite une carte pour tous les mots de longueur n et saisissez-la avec la distance. La valeur est une liste de mots de longueur n qui donnent cette distance particulière.

Vous allez créer une carte pour chaque distance.

Ensuite, votre recherche devient l'algorithme suivant:

  1. Utilisez la carte du dictionnaire correcte en fonction de la longueur du mot
  2. Calculer la longueur du vecteur de votre mot
  3. Rechercher la liste des mots qui correspondent à cette longueur
  4. Parcourez la liste et choisissez les anagrammes en utilisant un algorithme naïf, la liste des candidats est considérablement réduite
2
mprivat
static void Main(string[] args)
{

    string str1 = "Tom Marvolo Riddle";
    string str2 = "I am Lord Voldemort";

    str2=  str2.Replace(" ", string.Empty);
    str1 = str1.Replace(" ", string.Empty);
    if (str1.Length != str2.Length)
        Console.WriteLine("Strings are not anagram");
    else
    {
        str1 = str1.ToUpper();
        str2 = str2.ToUpper();
        int countStr1 = 0;
        int countStr2 = 0;
        for (int i = 0; i < str1.Length; i++)
        {
            countStr1 += str1[i];
            countStr2 += str2[i];

        }
        if(countStr2!=countStr1)
            Console.WriteLine("Strings are not anagram");
        else Console.WriteLine("Strings are  anagram");

    }
    Console.Read();
}
1
KrtkNyk
  • Réduisez les mots en - disons - minuscules (clojure.string/lower-case).
  • Classez-les (group-by) par lettre-fréquence (frequencies).
  • Déposer les cartes de fréquences, 
  • ... laissant les collections d'anagrammes.

(These) sont les fonctions correspondantes dans le dialecte LISP Clojure. 

La fonction entière peut être exprimée ainsi: 

(defn anagrams [dict]
  (->> dict
       (map clojure.string/lower-case)
       (group-by frequencies)
       vals))

Par exemple, 

(anagrams ["Salt" "last" "one" "eon" "plod"])
;(["salt" "last"] ["one" "eon"] ["plod"])

Une fonction d’indexation qui associe chaque élément à sa collection est

(defn index [xss]
  (into {} (for [xs xss, x xs] [x xs])))

Pour que, par exemple, 

((comp index anagrams) ["Salt" "last" "one" "eon" "plod"])
;{"salt" ["salt" "last"], "last" ["salt" "last"], "one" ["one" "eon"], "eon" ["one" "eon"], "plod" ["plod"]}

... où comp est l'opérateur de composition fonctionnelle. 

1
Thumbnail

Tentez bien, il serait plus facile de vérifier si le mot existe ..__ Donc, si vous mettez tout le dictionnaire dans un trie:

http://en.wikipedia.org/wiki/Trie

vous pouvez ensuite prendre votre mot et faire un retour en arrière simple en prenant un caractère et en vérifiant récursivement si nous pouvons "marcher" sur le Trie avec une combinaison du reste des caractères (en ajoutant un caractère à la fois). Lorsque tous les caractères sont utilisés dans une branche de récursivité et qu'il existe un chemin valide dans le Trie, le mot existe.

Le Trie aide car c'est une condition d'arrêt de Nice: Nous pouvons vérifier si la partie d'une chaîne, par exemple "Anag" est un chemin valide dans le fichier, sinon nous pouvons casser cette branche récursive. Cela signifie que nous n'avons pas à vérifier chaque permutation des personnages.

En pseudo-code

checkAllChars(currentPositionInTrie, currentlyUsedChars, restOfWord)
    if (restOfWord == 0)
    {
         AddWord(currentlyUsedChar)
    }
    else 
    {
        foreach (char in restOfWord)
        {
            nextPositionInTrie = Trie.Walk(currentPositionInTrie, char)
            if (nextPositionInTrie != Positions.NOT_POSSIBLE)
            {
                checkAllChars(nextPositionInTrie, currentlyUsedChars.With(char), restOfWord.Without(char))
            }
        }   
    }

Évidemment, vous avez besoin d'une structure de données Nice Trie qui vous permet de "parcourir" progressivement l'arbre et de vérifier à chaque nœud s'il existe un chemin d'accès avec le caractère donné vers un nœud suivant ... 

1
Daniel

essayé de mettre en œuvre la solution hashmap

public class Dictionary {

    public static void main(String[] args){

    String[] Dictionary=new String[]{"dog","god","tool","loot","rose","sore"};

    HashMap<String,String> h=new HashMap<String, String>();

    QuickSort q=new QuickSort();

    for(int i=0;i<Dictionary.length;i++){

        String temp =new String();

        temp= q.quickSort(Dictionary[i]);//sorted Word e.g dgo for dog

        if(!h.containsKey(temp)){
           h.put(temp,Dictionary[i]);
        }

        else
        {
           String s=h.get(temp);
           h.put(temp,s + " , "+ Dictionary[i]);
        }
    }

    String Word=new String(){"tolo"};

    String sortedword = q.quickSort(Word);

    if(h.containsKey(sortedword.toLowerCase())){ //used lowercase to make the words case sensitive

        System.out.println("anagrams from Dictionary   :  " + h.get(sortedword.toLowerCase()));
    }

}
0
megha

Cela dépend de la façon dont vous stockez votre dictionnaire. S'il s'agit d'un simple tableau de mots, aucun algorithme ne sera plus rapide que linéaire.

Si le problème est résolu, voici une approche qui peut fonctionner. Je l'ai inventé tout à l'heure, mais je suppose que c'est plus rapide que l'approche linéaire.

  1. Indiquez que votre dictionnaire est D, le préfixe actuel est S. S = 0;
  2. Vous créez une carte de fréquence pour votre mot. Permet de le noter par F.
  3. Utilisation de la recherche binaire pour rechercher des pointeurs au début de chaque lettre du dictionnaire. Notons ce tableau de pointeurs par P.
  4. Pour chaque caractère c de A à Z, si F [c] == 0, ignorez-le, sinon
    • S + = c;
    • F [c] -;
    • P <- pour chaque caractère i P [i] = pointeur sur le premier mot commençant par S + i.
    • Appelez récursivement l'étape 4 jusqu'à ce que vous trouviez une correspondance pour votre mot ou que vous constatiez qu'aucune correspondance n'existe.

Voici comment je le ferais, de toute façon. Il devrait y avoir une approche plus conventionnelle, mais elle est plus rapide que linéaire.

0
Saage
  • Calculez le vecteur de comptage de fréquence pour chaque mot du dictionnaire, un vecteur de longueur de la liste alphabétique.
  • générer un vecteur aléatoire gaussien de la longueur de la liste alphabétique
  • projetez le vecteur de comptage de chaque mot du dictionnaire dans cette direction aléatoire et stockez la valeur (insérez-la de sorte que le tableau de valeurs soit trié).

  • Avec un nouveau mot de test, projetez-le dans la même direction aléatoire que celle utilisée pour les mots du dictionnaire.

  • Effectuez une recherche binaire pour trouver la liste des mots qui correspondent à la même valeur.
  • Vérifiez si chaque mot obtenu comme ci-dessus est bien un anagramme vrai. Sinon, supprimez-le de la liste.
  • Renvoie les éléments restants de la liste.

PS: La procédure ci-dessus est une généralisation de la procédure des nombres premiers pouvant potentiellement conduire à des nombres élevés (et donc à des problèmes de précision de calcul).

0
Vedarun

Générer toutes les permutations est facile, je suppose que vous craignez que vérifier leur existence dans le dictionnaire soit la partie "très inefficace". Mais cela dépend en fait de la structure de données que vous utilisez pour le dictionnaire: bien entendu, une liste de mots serait inefficace pour votre cas d'utilisation. En parlant de Essais , ils seraient probablement une représentation idéale et très efficace également.

Une autre possibilité consisterait à effectuer un pré-traitement sur votre dictionnaire, par exemple. construisez une table de hachage où les clés sont les lettres du mot triées et les valeurs sont des listes de mots. Vous pouvez même sérialiser cette hashtable pour pouvoir l'écrire dans un fichier et recharger rapidement plus tard. Ensuite, pour rechercher des anagrammes, il vous suffit de trier votre mot et de rechercher l'entrée correspondante dans la table de hachage.

0
Artyom