2009-09-15 5 views
0

Le titre de ce type est maladroit; Je n'étais pas vraiment sûr de savoir comment résumer cela. Je sais comment je peux faire cela, je ne suis pas sûr de savoir comment le faire efficacement. Voici mon problème:Rechercher des permutations de chaînes dans le jeu de chaînes

J'ai une chaîne en entrée. Disons que:

foo bar

Et j'ai un très grand ensemble de cordes (des dizaines de milliers). Disons que:

foo, baz, bar, bla, foo bar, foo baz

je dois correspondre à l'entrée des chaînes dans l'ensemble. Dans ce cas, "foo", "bar" et "foo bar" sont considérés comme des correspondances. Donc, j'ai besoin de soit chercher d'une manière ou d'une autre toutes les permutations de l'entrée (il pourrait être plus long que 2 mots), ou d'une façon ou d'une autre détecter si l'utilisateur voulait le mettre entre guillemets. Ou peut-être faire quelque chose que je n'ai pas pensé.

Existe-t-il une sorte de structure de données ou d'algorithme que je peux utiliser pour cela? Comment dois-je procéder, ou ne dois-je pas gérer ce cas d'utilisation?

EDIT: Une faute de frappe a déformé le problème; Dans l'exemple ci-dessus, "foo baz" est aussi un match. Désolé pour ça. Je veux essentiellement faire correspondre toute permutation des mots d'entrée au dictionnaire. Ainsi, une entrée de "abc xyz" correspondra à "123 abc" ou "abc xyz" ou "xyz 123", mais pas "abcxyz".

+0

sont les chaînes de mots anglais ou peuvent-ils contenir des caractères? Sont-ils sensibles à la casse? – Adamski

+0

@Adamski Ce sont des mots anglais et ne sont pas sensibles à la casse; Cependant, ce sont des mots très techniques, comme des choses que vous ne trouveriez pas dans un dictionnaire. –

+0

Si le dictionnaire contenait "foob" cela serait-il retourné si je cherchais "foo" ou êtes-vous uniquement concerné par des correspondances exactes? – Adamski

Répondre

2

Je suggère d'utiliser un dictionnaire. Utilisez des chaînes comme clés et une liste de chaînes comme valeur. Marquez les chaînes qui seront recherchées et ajoutez la chaîne entière à votre dictionnaire une fois pour chaque jeton. (Youn peut utiliser la méthode split pour marquer vos chaînes.Utilisez l'espace comme délimiteur.) Par la suite, chaque fois que vous avez besoin de rechercher, vous marquez la chaîne de recherche et effectuez une recherche pour chaque token dans votre dictionnaire.

Ainsi, si vous avez ajouté les chaînes suivantes: foo, baz, bar, blah, bar foo, foo baz

Votre dictionnaire contient des entrées:

foo: foo, foo bar, foo baz baz : baz, foo bar baz : bar, foo bar bla: bla

Si vous recherchez alors pour "foo bar",

votre sortie est l'union des entrées stockées sous foo et bar comme donc: "foo bar": = foo, bar

foo: foo, foo bar, foo baz union bar: bar, foo bar

donnant: foo, foo bar, foo baz, bar

EDIT: Je viens de remarquer que vous voulez seulement des correspondances complètes ou partielles, c'est-à-dire que foo baz n'est pas acceptable.La solution facile consiste à post-traiter les résultats - limiter la longueur de la chaîne de recherche et de la chaîne cible à la longueur de la chaîne la plus courte, puis comparer la chaîne tronquée avec la chaîne non modifiée. Acceptez seulement ceux qui sont équivalents.

EDIT: Donc, il s'avère que foo baz est en effet un match. Ne tenez pas compte du paragraphe ci-dessus (première édition). Voir (C#) Code comme suit:

class DictionarySearch 
{ 
    private Dictionary<string, List<string>> dict; 

    public DictionarySearch() 
    { 
     dict = new Dictionary<string, List<string>>(); 
    } 

    /// <summary> 
    /// Add a string e.g. foo bar to the dictionary 
    /// </summary> 
    /// <param name="s">string to be added</param> 
    public void addString(string s) 
    { 
     //tokenize string 
     string[] words = s.Split(new char[] { ' ' }); 

     //add each token to the dictionary as a key with the matching value being s 
     foreach (string w in words) 
     { 
      if (dict.ContainsKey(w)) 
      { 
       dict[w].Add(s); 
      } 
      else 
      { 
       dict.Add(w, new List<string>()); 
       dict[w].Add(s); 
      } 
     } 
    } 
    /// <summary> 
    /// Find all strings which match at least one token 
    /// </summary> 
    /// <param name="s">string of tokens (words) to be matched</param> 
    /// <returns>List of strings matching at least one word</returns> 
    public IList<string> getMatches(string s) 
    { 
     //split search string into words 
     string[] words = s.Split(new char[] { ' ' }); 
     List<string> output = new List<string>(); 

     //retrieve from dictionary list of strings matching each word. 
     foreach (string w in words) 
     { 
      if (dict.ContainsKey(w)) 
      { 
       output.AddRange(dict[w]); 
      } 
      else 
      { 
       continue; 
      } 
     } 

     return output; 
    } 
} 

Étant donné un dictionnaire avec des chaînes m avec des mots q par chaîne et n mots uniques, et une chaîne de recherche avec des mots l la complexité du temps sont les suivants:

Remplir la structure de données: O (q m T [dictionnaire-insert]). Une insertion doit être effectuée pour chaque mot

Trouvez une chaîne: O (l * T [dictionnaire-trouver]). Une recherche de dictionnaire par mot dans la chaîne de recherche.

Le coût réel dépend de l'implémentation de votre dictionnaire. Un dictionnaire basé sur une table de hachage entraîne un coût O (1) à la fois pour l'insertion et la recherche. Un dictionnaire basé sur un arbre binaire entraîne un coût O (lg n) à la fois pour l'insertion et la recherche.

1

Qu'est-ce que vous avez besoin est Lucene

+0

@Dennis: La page - www.google.com/?q=Lucene - n'existe pas. Peut-être que vous vouliez dire: http://lucene.apache.org/java/docs/ – CPerkins

0

Ce code fonctionne. Je ne sais pas si cela est assez efficace pour vous.

String[] dict = "foo bar".split(" "); 

    String[] array = new String[] { "foo", "baz", "bar", "blah", "foo bar", 
      "foo baz" }; 

    loop: for (String s : array) { 
     String[] a = s.split(" "); 

     for (String sample : dict) 
      for (String s1 : a) 
       if (sample.equals(s1)) { 
        System.out.println(s); 
        continue loop; 
       } 
    } 
1

(Quand vous dites « efficaces », vous avez besoin probablement d'être plus explicite en termes d'espace et de temps permet de supposer que vous voulez dire l'efficacité du temps (étant donné que vous avez mentionné les permutations)).

La tâche de calcul de la réponse à

String[] findStringsContaining(List<String> strings, String[] words) 

peut être partitionné et remis hors de fils parallèles d'exécution, étant donné qu'il est purement effet fonctionnel et le côté libre dans un étage intermédiaire, et les résultats rejoint comme une dernière étape. C'est à dire. vous pouvez partitionner à travers les mots, et/ou, la liste des chaînes.

Voici comment map-reduce fonctionne (et votre cas, son hors de propos que sa se passe tout sur la même machine.)

Votre mappeur (attribué à un fil pour chacun des mots) est:

boolean [] stringContainsWord (List<String> strings, String word); 

Cette méthode s'exécuterait en parallèle.

Le tableau booléen aurait alors une valeur true pour chaque index (de List) correspondant au mot donné.

et votre réducteur (courir après tous les cartographes ont fini) est:

List<String> getMatchingList(List<String>, List<boolean[]> mapperResults); 

Mettant de côté les frais généraux pour les fils et en supposant des coûts négligeables pour nombre de fils de mappeur pour un nombre raisonnable de mots d'entrée, cela donnerait vous un processus de temps O (n) (pour le mappeur) + O (m) (pour le réducteur), où n est le nombre d'éléments dans votre liste de chaînes, et m est le nombre de mots dans votre entrée.

Vous pouvez paralléliser davantage la tâche en partitionnant votre liste de chaînes et en exécutant p threads pour chacun des mots, et en faisant rechercher chaque thread dans un sous-ensemble de votre liste de chaînes, afin que la liste d'entrée de votre mappeur soit 1/p éléments de la liste globale.

-

Une autre approche que vous pouvez envisager, surtout si la liste des chaînes est énorme, et le contenu est langauge (comme l'anglais), est d'optimiser compte tenu du fait que la plupart des langues avoir un assez petit ensemble de mots qui constituent l'essentiel des phrases dans cette langue. Par exemple, si votre liste contient 2 millions de phrases en anglais, il y a de fortes chances que la liste des mots uniques soit beaucoup plus petite (disons quelques centaines).

Dans ce cas, vous pouvez avoir une carte de mot -> phrases, et le test des phrases correspondantes pour un mot donné est réduit à une recherche dans la carte.

(Notez que vous pouvez toujours combiner l'approche initiale à ce sujet.)

0

de l'idée de ejspencer je mis cela ensemble

// Build the dictionary/data structure 
// O([average split length]*n) 
public static Dictionary<String,List<int>> BuildDictionary(String[] data) 
{ 
    String[] temp; 
    Dictionary<String,List<int>> dict = new Dictionary<String,List<int>>(); 
    for(int i = 0; i < data.length; i++) 
    { 
     temp = data[i].split(" "); 
     for(int j = 0; j < temp.length; j ++) 
     { 
      if(dict.get(temp[j]) == null) 
       dict.put(temp[j],new List<int>()); 

      dict.get(temp[j]).add(i); 
     } 
    } 

    return dict; 
} 

// find all the matches 
// O([average number of matches per key]*[input split length]) 
public static List<int> FindMatches(String input, Dictionary<String,List<int> dict) 
{ 
    String[] temp = input.split(" "); 
    List<int> ret = new List<int>(); 

    for(int i = 0; i < temp.length; i++) 
    { 
     if(dict.get(temp[i]) == null) 
      continue; // no match 

     // read the match into the return list, ignore copies 
     List<int> match = dict.get(temp[i]); 
     for(int j = 0; j < match.count(); j++) 
      if(!ret.contains(match.get(i)) 
       ret.add(match.get(i)); 
    } 

    return ret; 
} 

ne sera probablement pas compiler tout de suite, mais je me dis que vous » De toute façon, il va falloir le faire avec ça et cela vous donne une bonne idée pour un accès rapide et un code simple (pas d'infraction alphazero).

Cette recherche est sensible à la casse, mais vous pouvez tout aussi facilement utiliser toUpper ou toLower pour le modifier.

2

Quelle est la taille de votre dictionnaire? Vous pouvez convertir votre dictionnaire en trie. Il y a eu des messages par des gens comment convertir un dictionnaire en trie. Une fois que vous faites cela, la recherche est simple et rapide. En outre, une solution simple pourrait être de diviser la chaîne de recherche en mots séparés, et de rechercher chacun d'entre eux dans votre trie, en s'assurant que les doublons ne sont pas considérés deux fois.

1

Pour les chaînes d'entrée de grande taille et les dictionnaires contenant des expressions de plusieurs mots, utilisez les algos Rabin-Karp ou Aho-Corasick.

(Lien vers Rabin-Karp - http://en.wikipedia.org/wiki/Rabin -Karp_string_search_algorithm - pour une raison quelconque, je ne pouvais pas obtenir un lien hypertexte la référence ci-dessus)