¿Cómo encontrar una palabra a partir de matrices de caracteres?

¿Cuál es la mejor manera de resolver esto?

Tengo un grupo de matrices con 3-4 caracteres dentro de cada uno así:

{p,     {a,    {t,    {m,
 q,      b,     u,     n,
 r,      c      v      o
 s      }      }      }
}

También tengo una variedad de palabras en el diccionario.

¿Cuál es la forma mejor / más rápida de encontrar si la matriz de caracteres se puede combinar para formar una de las palabras del diccionario? Por ejemplo, las matrices anteriores podrían formar las palabras:

"palmadita", "rata", "a", "a", "bum" (risas)
pero no "nudo" o "tapete"

¿Debería recorrer el diccionario para ver si se pueden formar palabras u obtener todas las combinaciones de las letras y luego compararlas con el diccionario?

preguntado el 16 de mayo de 11 a las 20:05

¿Cuál es el tamaño total de la matriz, cuál es el tamaño de su diccionario? -

no, estoy creando una aplicación para iPhone, así que no quiero dar todos los detalles, -

el diccionario es 200,000 y la matriz contiene 10 matrices de matrices de 3-4 letras -

200k palabras, lo obtuve del archivo mac / usr / share / dict / words, muchas de ellas no son realmente palabras, pero fue la más fácil de obtener

@GWW, si fuera tarea, probablemente sería una pregunta mejor formada:

4 Respuestas

Tenía algo de código de Scrabble por ahí, así que pude armar esto. El diccionario que utilicé es sowpods (267751 palabras). El siguiente código lee el diccionario como un archivo de texto con una palabra en mayúscula en cada línea.

El código es C #:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace SO_6022848
{
  public struct Letter
  {
    public const string Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    public static implicit operator Letter(char c)
    {
      return new Letter() { Index = Chars.IndexOf(c) };
    }
    public int Index;
    public char ToChar()
    {
      return Chars[Index];
    }
    public override string ToString()
    {
      return Chars[Index].ToString();
    }
  }

  public class Trie
  {
    public class Node
    {
      public string Word;
      public bool IsTerminal { get { return Word != null; } }
      public Dictionary<Letter, Node> Edges = new Dictionary<Letter, Node>();
    }

    public Node Root = new Node();

    public Trie(string[] words)
    {
      for (int w = 0; w < words.Length; w++)
      {
        var word = words[w];
        var node = Root;
        for (int len = 1; len <= word.Length; len++)
        {
          var letter = word[len - 1];
          Node next;
          if (!node.Edges.TryGetValue(letter, out next))
          {
            next = new Node();
            if (len == word.Length)
            {
              next.Word = word;
            }
            node.Edges.Add(letter, next);
          }
          node = next;
        }
      }
    }

  }

  class Program
  {
    static void GenWords(Trie.Node n, HashSet<Letter>[] sets, int currentArrayIndex, List<string> wordsFound)
    {
      if (currentArrayIndex < sets.Length)
      {
        foreach (var edge in n.Edges)
        {
          if (sets[currentArrayIndex].Contains(edge.Key))
          {
            if (edge.Value.IsTerminal)
            {
              wordsFound.Add(edge.Value.Word);
            }
            GenWords(edge.Value, sets, currentArrayIndex + 1, wordsFound);
          }
        }
      }
    }

    static void Main(string[] args)
    {
      const int minArraySize = 3;
      const int maxArraySize = 4;
      const int setCount = 10;
      const bool generateRandomInput = true;

      var trie = new Trie(File.ReadAllLines("sowpods.txt"));
      var watch = new Stopwatch();
      var trials = 10000;
      var wordCountSum = 0;
      var rand = new Random(37);

      for (int t = 0; t < trials; t++)
      {
        HashSet<Letter>[] sets;
        if (generateRandomInput)
        {
          sets = new HashSet<Letter>[setCount];
          for (int i = 0; i < setCount; i++)
          {
            sets[i] = new HashSet<Letter>();
            var size = minArraySize + rand.Next(maxArraySize - minArraySize + 1);
            while (sets[i].Count < size)
            {
              sets[i].Add(Letter.Chars[rand.Next(Letter.Chars.Length)]);
            }
          }
        }
        else
        {
          sets = new HashSet<Letter>[] { 
          new HashSet<Letter>(new Letter[] { 'P', 'Q', 'R', 'S' }), 
          new HashSet<Letter>(new Letter[] { 'A', 'B', 'C' }), 
          new HashSet<Letter>(new Letter[] { 'T', 'U', 'V' }), 
          new HashSet<Letter>(new Letter[] { 'M', 'N', 'O' }) };
        }

        watch.Start();
        var wordsFound = new List<string>();
        for (int i = 0; i < sets.Length - 1; i++)
        {
          GenWords(trie.Root, sets, i, wordsFound);
        }
        watch.Stop();
        wordCountSum += wordsFound.Count;
        if (!generateRandomInput && t == 0)
        {
          foreach (var word in wordsFound)
          {
            Console.WriteLine(word);
          }
        }
      }
      Console.WriteLine("Elapsed per trial = {0}", new TimeSpan(watch.Elapsed.Ticks / trials));
      Console.WriteLine("Average word count per trial = {0:0.0}", (float)wordCountSum / trials);
    }

  }

}

Aquí está el resultado al usar sus datos de prueba:

PA
PAT
PAV
QAT
RAT
RATO
RAUN
SAT
SAU
SAV
SCUM
AT
AVO
BUM
BUN
CUM
TO
UM
UN
Elapsed per trial = 00:00:00.0000725
Average word count per trial = 19.0

Y la salida cuando se usan datos aleatorios (no imprime cada palabra):

Elapsed per trial = 00:00:00.0002910
Average word count per trial = 62.2

EDIT: Lo hice mucho más rápido con dos cambios: Almacenar la palabra en cada nodo terminal del trie, para que no tenga que reconstruirse. Y almacenar las letras de entrada como una matriz de conjuntos hash en lugar de una matriz de matrices, para que la llamada Contains () sea rápida.

contestado el 17 de mayo de 21 a las 10:05

¿Alguien puede traducir esto al objetivo c? = / - GeeGoldz

Una observación: en esta implementación, un Dictionary se crea para todos los nodos posibles del trie, que tiene mucha memoria. Una posible optimización sería crear un uso de una lista o matriz para almacenar los nodos secundarios (que puede mantener ordenados mediante una búsqueda binaria) o, mejor aún, vincular los nodos entre sí utilizando algunos previous y next referencias (como en un linked list). Sé que una lista (y una lista vinculada) tienen O(N) rendimiento (vs O(1) esperando Dictionary) pero dado que el número de elementos es bastante pequeño aquí (máximo el número de letras en el alfabeto) no dolerá tanto. - tigrou

Entonces, será aproximadamente 26/2 = 13 veces más lento y ¿a eso le parece que no duele mucho? - Fantius

Dictionary no es realmente O(1) (hay colisiones). También debe realizar varias cosas antes de recuperar un elemento (como calcular el hashcode). Si tomo su ejemplo, eso significaría verificar si un valor está en un List de 4 elementos sería 2 veces más lento que usar un Dictionary (mientras List lo derrotará, iterar a través de 4 elementos no es nada). En el caso de la trie, si usando List es más lento, pero definitivamente no 13 veces más lento. Y si mantiene la lista ordenada, puede realizar una búsqueda binaria. Tomará O(log n) que para 26 letras significa 5 iteraciones. - tigrou

Trie útil para otra respuesta SO: stackoverflow.com/a/47277064/154355. Sin embargo, Trie tiene un error al agregar una palabra que es una subcadena de una palabra existente. Por ejemplo, agregue Emergencia y luego Emerger. El Trie en realidad no agregará Emerge a la lista, porque solo puede tener una palabra en la terminal. Mi modificación (ver dotnetfiddle.net/jGbnCH) permite almacenar palabras en nodos intermedios. - Mark Glasgow

Probablemente hay muchas formas de resolver esto.

Lo que te interesa es el número de cada personaje tiene disponible para formar una palabra, y cuántos de cada carácter se requieren para cada palabra del diccionario. El truco consiste en buscar eficazmente esta información en el diccionario.

Quizás puedas usar un árbol de prefijos (un intento), algún tipo de tabla hash inteligente o similar.

De todos modos, probablemente tendrá que probar todas sus posibilidades y compararlas con el diccionario. Es decir, si tiene tres matrices de tres valores cada una, habrá 3 ^ 3 + 3 ^ 2 + 3 ^ 1 = 39 combinaciones para verificar. Si este proceso es demasiado lento, tal vez pueda Filtro de floración delante del diccionario, para comprobar rápidamente si una palabra definitivamente no está en el diccionario.

EDIT: De todos modos, ¿no es esto esencialmente lo mismo que Scrabble? Quizás intente buscar en Google "algoritmo scrabble"te dará algunas buenas pistas.

contestado el 20 de mayo de 11 a las 14:05

¡Muchas gracias !, lo intentaré - GeeGoldz

+1 - Sin embargo, parece que aún tendrás que hacer algún tipo de DFS en el intento. En teoría, esto podría tomar O (# caracteres en el diccionario) pero apuesto a que en la práctica funcionaría bien. Generadores de anagramas (por ejemplo, Scrabble) Creo que generan todas las permutaciones de las letras, lo que probablemente no sea factible para 30-40 caracteres / - DFB

@spinning_plate: Sí, pensé que esto era fácil, pero parece que en realidad puede ser un poco complicado. ¿Quizás un DAG podría funcionar también y ser eficiente? - csl

es algo similar a scrabble pero el usuario ingresará un número en el que cada dígito corresponde a uno de los grupos de letras - GeeGoldz

@csl: creo que el trie es el camino correcto (aunque un trie es un DAG, así que tal vez solo estábamos diciendo lo mismo). Lo habrías atravesado de manera diferente. Cree un recuento de todas las letras de entrada, recorra al siguiente nodo solo si tenemos el carácter, decremento y recurse. - DFB

La pregunta reformulada puede responderse simplemente generando y probando. Como tiene 4 letras y 10 matrices, solo tiene alrededor de 1 millón de combinaciones posibles (10 millones si permite un carácter en blanco). Necesitará una forma eficiente de buscarlos, usar un BDB o algún tipo de hash basado en disco.

La solución trie publicada anteriormente también debería funcionar, simplemente está más restringido por los caracteres que puede elegir en cada paso de la búsqueda. También debería ser más rápido.

contestado el 17 de mayo de 11 a las 02:05

Acabo de hacer un bucle for anidado muy grande como este:

for(NSString*s1 in [letterList objectAtIndex:0]{
    for(NSString*s2 in [letterList objectAtIndex:1]{
       8 more times...
    }
}

Luego hago una búsqueda binaria en la combinación para ver si está en el diccionario y la agrego a una matriz si está

contestado el 24 de mayo de 11 a las 23:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.