【问题标题】:How to find a word from arrays of characters?如何从字符数组中找到一个单词?
【发布时间】:2011-08-26 17:48:53
【问题描述】:

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

我有一组数组,每个数组包含 3-4 个字符,如下所示:

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

我还有一个字典单词数组。

如果字符数组可以组合成字典单词之一,最好/最快的方法是什么?例如,上面的数组可以使单词:

"pat","rat","at","to","bum"(lol)
但不是“nub”或“mat”

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

【问题讨论】:

  • 数组的总大小是多少,你的字典大小是多少?
  • 不,我正在制作一个 iphone 应用,所以我不想提供全部细节,
  • 字典为200,000,数组包含10个3-4个字母数组的数组
  • 200k 字,我是从 mac /usr/share/dict/words 文件中得到的,其中很多不是真正的单词,但它是最容易获得的
  • @GWW,如果是家庭作业,这可能是一个更好的问题

标签: algorithm dictionary


【解决方案1】:

我有一些拼字游戏代码,所以我可以把它放在一起。我使用的字典是 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() 调用速度很快。

【讨论】:

  • 谁能把它翻译成目标c? =/
  • 备注:在这个实现中,为trie 的所有可能节点创建了一个Dictionary,这非常耗内存。一种可能的优化是创建使用列表或数组来存储子节点(您可以使用二进制搜索保持排序),或者更好的是,使用一些 previousnext 引用将节点链接在一起(就像在 @ 987654330@)。我知道一个列表(和一个链表)具有O(N) 的性能(与O(1)Dictionary 相比)但由于这里的元素数量非常少(字母表中的最大字母数)它不会受到伤害这么多。
  • 所以,它会慢大约 26/2=13 倍,你说这不会造成太大伤害吗?
  • Dictionary 并不是真正的O(1)(存在冲突)。它还需要在检索元素之前执行几件事(例如计算hashcode)。如果我以你的例子为例,这意味着检查一个值是否在 4 个元素的 List 中将比使用 Dictionary 慢 2 倍(而 List 将击败它,迭代槽 4 个元素没什么) .对于trie,是的,使用List 会更慢,但绝对不会慢13 倍。如果您保持列表排序,您可以执行二进制搜索。它需要O(log n),26 个字母意味着 5 次迭代。
  • 五。有用的尝试另一个 SO 答案:stackoverflow.com/a/47277064/154355。但是,Trie 在添加作为现有单词的子字符串的单词时存在错误。例如,添加紧急情况然后出现。 Trie 实际上不会将 Emerge 添加到列表中,因为您只能在终端上输入一个单词。我的修改(参见dotnetfiddle.net/jGbnCH)允许将单词存储在中间节点。
【解决方案2】:

我刚刚做了一个非常大的嵌套 for 循环,如下所示:

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

然后我对该组合进行二分搜索,看看它是否在字典中,如果是则将其添加到数组中

【讨论】:

    【解决方案3】:

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

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

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

    无论如何,您可能必须尝试所有的可能性并对照字典进行核对。即,如果您有三个数组,每个数组包含三个值,则将有 3^3+3^2+3^1=39 组合要检查。如果这个过程太慢,那么也许你可以在字典前面贴一个Bloom filter,以快速检查一个词是否肯定不在字典中。

    编辑: 无论如何,这不是和 Scrabble 本质上一样吗?也许尝试谷歌搜索“scrabble algorithm”会给你一些很好的线索。

    【讨论】:

    • 非常感谢!我会试试的
    • +1 - 不过,您似乎仍然需要在 trie 上执行某种 DFS。从理论上讲,这可能需要 O(字典中的#个字符),但我敢打赌在实践中它会很好地工作。字谜生成器(例如,拼字游戏)我认为生成字母的所有排列,这对于 30-40 个字符可能是不可行的/
    • @spinning_plate:是的,我认为这很容易,但实际上似乎有点棘手。也许 DAG 也可以工作并且高效?
    • 有点类似于拼字游戏,但用户会输入一些数字,其中每个数字对应一个字母组
    • @csl - 我认为 trie 是正确的轨道(虽然 trie 是 DAG,所以也许只是在说同样的事情)。你只是以不同的方式遍历它。创建所有输入字母的计数,只有当我们有字符时才遍历下一个节点,递减和递归。
    【解决方案4】:

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

    之前发布的 trie 解决方案也应该可以使用,只是您在搜索的每个步骤中可以选择的字符更多地受到限制。它也应该更快。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-02-16
      • 2022-01-09
      • 1970-01-01
      • 1970-01-01
      • 2021-11-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多