如何从字符数组中查找单词?

时间:2011-05-16 20:18:42

标签: algorithm dictionary

解决此问题的最佳方法是什么:

我有一组数组,里面各有3-4个字符,如下所示:

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

我也有一系列字典词。

查找字符数组是否可以组合形成字典单词之一的最佳/最快方法是什么?例如,上面的数组可以生成单词:

“轻拍”,“老鼠”,“在”,“到”,“屁股”(lol)
但不是“小块”或“垫子”

我应该通过字典循环到看看是否可以制作单词或从字母中获取所有组合然后将它们与字典进行比较

4 个答案:

答案 0 :(得分:16)

我有一些拼字游戏代码,所以我可以将它们放在一起。我使用的字典是sowpods(267751字)。下面的代码将字典作为文本文件读取,每行包含一个大写字。

代码是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);
    }

  }

}

以下是使用测试数据时的输出:

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

使用随机数据时的输出(不打印每个单词):

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

编辑:我通过两次更改让它变得更快:在trie的每个终端节点上存储单词,这样就不必重建它。并将输入字母存储为散列集数组而不是数组数组,以便Contains()调用快速。

答案 1 :(得分:3)

解决这个问题的方法可能很多。

您感兴趣的是您可以形成单词的每个字符的数量,以及每个字典单词需要多少个字符。诀窍是如何在字典中有效地查找这些信息。

也许你可以使用前缀树(a trie),某种智能哈希表或类似的东西。

无论如何,您可能必须尝试所有可能性并根据字典检查它们。即,如果你有三个三个数组的数组,那么将有3 ^ 3 + 3 ^ 2 + 3 ^ 1 = 39个组合来检查。如果这个过程太慢,那么也许你可以在字典前加上Bloom filter,以便快速检查一个单词是否绝对不在字典中。

编辑:无论如何,这与拼字游戏基本上不一样吗?也许尝试谷歌搜索“scrabble algorithm”会给你一些很好的线索。

答案 2 :(得分:1)

重新提出的问题可以通过生成和测试来回答。由于你有4个字母和10个数组,你只有大约100万个可能的组合(如果允许空白字符,则为1000万个)。您需要一种有效的方法来查找它们,使用BDB或某种基于磁盘的哈希。

之前发布的trie解决方案也应该可以正常工作,您可以在搜索的每个步骤中选择哪些字符来限制您。它应该更快。

答案 3 :(得分:1)

我刚刚制作了一个非常大的嵌套for循环:

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

然后我对组合进行二元搜索以查看它是否在字典中并将其添加到数组中(如果它是