获取所有子串(拼字游戏)字符串的所有单词列表的算法?

时间:2009-05-19 01:16:42

标签: substring anagram

例如,如果输入字符串是helloworld,我希望输出如下:

do
he
we
low
hell
hold
roll
well
word
hello
lower
world
...

一直到最长的单词,即helloworld子字符串的字谜。就像Scrabble一样。 输入字符串可以是任意长度,但很少超过16个字符。

我已经完成了搜索,并提出了像特里的结构,但我仍然不确定如何实际执行此操作。

8 个答案:

答案 0 :(得分:14)

用于保存有效条目字典的结构将对效率产生巨大影响。将它组织为树,root是单个零字母“word”,空字符串。 root的每个子节点都是一个可能单词的单个首字母,其中的子节点是可能单词的第二个字母等,每个节点都标记为是否实际形成单词。

您的测试人员功能将是递归的。它以零字母开头,从有效条目的树中找到“”不是一个单词,但它确实有子项,所以你用你的起始单词(没有字母)递归地调用你的测试者,你的每一个可用的剩余字母都是输入字符串(在那一点上都是它们)。如果有效,请检查树中的每个单字母条目;如果孩子,重新调用测试器功能附加每个剩余的可用字母,等等。

例如,如果您的输入字符串是“helloworld”,那么您将首先使用“”调用递归测试器函数,并将剩余的可用字母“helloworld”作为第二个参数传递。函数看到“”不是单词,但是孩子“h”确实存在。所以它称自己为“h”和“elloworld”。功能看到“h”不是单词,但是孩子“e”存在。所以它称自己为“他”和“lloworld”。函数看到“e”被标记,所以“他”是一个单词,请注意。此外,孩子“l”存在,所以下一个呼叫是“hel”与“loworld”。它接下来会发现“地狱”,然后是“你好”,然后必须退出并可能接下来找到“空心”,然后再一次支持空字符串,然后接着以“e”字开头。

答案 1 :(得分:9)

我无法抗拒自己的实施。它通过按字母顺序对所有字母进行排序,并将它们映射到可以从中创建的单词来创建字典。这是一个O(n)启动操作,无需查找所有排列。您可以将字典实现为另一种语言的trie,以获得更快的加速。

“getAnagrams”命令也是一个O(n)操作,它搜索字典中的每个单词以查看它是否是搜索的子集。做getAnagrams(“无线电报”)“(一个20个字母的单词)在我的笔记本电脑上花了大约1秒钟,并返回了1496个字谜。

# Using the 38617 word dictionary at 
# http://www.cs.umd.edu/class/fall2008/cmsc433/p5/Usr.Dict.Words.txt
# Usage: getAnagrams("helloworld")

def containsLetters(subword, word):
    wordlen = len(word)
    subwordlen = len(subword)

    if subwordlen > wordlen:
        return False

    word = list(word)
    for c in subword:
        try:
            index = word.index(c)
        except ValueError:
            return False
        word.pop(index)
    return True

def getAnagrams(word):
    output = []
    for key in mydict.iterkeys():
        if containsLetters(key, word):
            output.extend(mydict[key])

    output.sort(key=len)
    return output

f = open("dict.txt")
wordlist = f.readlines()
f.close()

mydict = {}
for word in wordlist:
    word = word.rstrip()
    temp = list(word)
    temp.sort()
    letters = ''.join(temp)

    if letters in mydict:
        mydict[letters].append(word)
    else:
        mydict[letters] = [word]

示例运行:

>>> getAnagrams("helloworld")
>>> ['do', 'he', 'we', 're', 'oh', 'or', 'row', 'hew', 'her', 'hoe', 'woo', 'red', 'dew', 'led', 'doe', 'ode', 'low', 'owl', 'rod', 'old', 'how', 'who', 'rho', 'ore', 'roe', 'owe', 'woe', 'hero', 'wood', 'door', 'odor', 'hold', 'well', 'owed', 'dell', 'dole', 'lewd', 'weld', 'doer', 'redo', 'rode', 'howl', 'hole', 'hell', 'drew', 'word', 'roll', 'wore', 'wool','herd', 'held', 'lore', 'role', 'lord', 'doll', 'hood', 'whore', 'rowed', 'wooed', 'whorl', 'world', 'older', 'dowel', 'horde', 'droll', 'drool', 'dwell', 'holed', 'lower', 'hello', 'wooer', 'rodeo', 'whole', 'hollow', 'howler', 'rolled', 'howled', 'holder', 'hollowed']

答案 2 :(得分:6)

您想要的数据结构称为Directed Acyclic Word Graph (dawg),Andrew Appel和Guy Jacobsen在他们的论文“世界上最快的拼字游戏计划”中对其进行了描述,遗憾的是他们选择不在网上免费提供。 ACM会员或大学图书馆将为您提供。

我已经用至少两种语言实现了这个数据结构 - 它简单,易于实现,而且速度非常快。

答案 3 :(得分:2)

您想要的是power set的实现。

另请参阅Eric Lipparts的博客,他在一段时间后发表了关于this very thing的博客

编辑:

这是我写的一个从给定字符串中获取powerset的实现...

private IEnumerable<string> GetPowerSet(string letters)
{
  char[] letterArray = letters.ToCharArray();
  for (int i = 0; i < Math.Pow(2.0, letterArray.Length); i++)
  {
    StringBuilder sb = new StringBuilder();
    for (int j = 0; j < letterArray.Length; j++)
    {
      int pos = Convert.ToInt32(Math.Pow(2.0, j));
      if ((pos & i) == pos)
      {
        sb.Append(letterArray[j]);
      }
    }
    yield return new string(sb.ToString().ToCharArray().OrderBy(c => c).ToArray());
  }
}

这个函数给了我构成传入字符串的字符的函数,然后我可以将它们用作字谜字典中的键......

Dictionary<string,IEnumerable<string>>

我创建了像这样的字谜字典...(可能有更有效的方式,但这很简单,而且足够快速,可以使用拼字游戏单词列表)

wordlist = (from s in fileText.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
                let k = new string(s.ToCharArray().OrderBy(c => c).ToArray())
                group s by k).ToDictionary(o => o.Key, sl => sl.Select(a => a));

答案 4 :(得分:2)

一种简单的方法是生成所有“子串”,并为每个子串检查它是否是可接受单词集的一个元素。例如,在Python 2.6中:

import itertools
import urllib

def words():
  f = urllib.urlopen(
    'http://www.cs.umd.edu/class/fall2008/cmsc433/p5/Usr.Dict.Words.txt')
  allwords = set(w[:-1] for w in f)
  f.close()
  return allwords

def substrings(s):
  for i in range(2, len(s)+1):
    for p in itertools.permutations(s, i):
      yield ''.join(p)

def main():
  w = words()
  print '%d words' % len(w)
  ss = set(substrings('weep'))
  print '%d substrings' % len(ss)
  good = ss & w
  print '%d good ones' % len(good)
  sgood = sorted(good, key=lambda w:(len(w), w))
  for aword in sgood:
    print aword

main()

会发出:

38617 words
31 substrings
5 good ones
we
ewe
pew
wee
weep

当然,正如其他回复指出的那样,有目的地组织数据可以大大加快运行时间 - 尽管快速anagram finder的最佳数据组织可能会有所不同......但这在很大程度上取决于性质你的词汇词典(几万,像这里 - 或数百万?)。应考虑散列图和“签名”(基于对每个单词中的字母进行排序),以及尝试&amp; c。

答案 5 :(得分:0)

Tim JEric Lippert的博文一样,我想到的第一件事就是。我想补充一点,他写了一篇关于如何改善他第一次尝试表现的后续行动。

答案 6 :(得分:0)

我相信this question答案中的Ruby代码也可以解决您的问题。

答案 7 :(得分:0)

我最近在手机上玩了很多Wordfeud,如果我能拿出一些代码给我一个可能的单词列表,我很好奇。以下代码使用您的可用源代码字母(*表示通配符)和带有允许单词主列表(TWL,SOWPODS等)的数组,并生成匹配列表。它通过尝试从源字母构建主列表中的每个单词来实现此目的。

我在编写代码后发现了这个主题,它绝对不如John Pirie的方法或DAWG算法那么高效,但它仍然很快。

public IList<string> Matches(string sourceLetters, string [] wordList)
{
    sourceLetters = sourceLetters.ToUpper();

    IList<string> matches = new List<string>();

    foreach (string word in wordList)
    {
        if (WordCanBeBuiltFromSourceLetters(word, sourceLetters))
            matches.Add(word);
    }

    return matches;
}


public bool WordCanBeBuiltFromSourceLetters(string targetWord, string sourceLetters)
{
    string builtWord = "";

    foreach (char letter in targetWord)
    {
        int pos = sourceLetters.IndexOf(letter);
        if (pos >= 0)
        {
            builtWord += letter;
            sourceLetters = sourceLetters.Remove(pos, 1);
            continue;
        }


        // check for wildcard
        pos = sourceLetters.IndexOf("*");
        if (pos >= 0)
        {
            builtWord += letter;
            sourceLetters = sourceLetters.Remove(pos, 1);
        }


    }

    return string.Equals(builtWord, targetWord);

}