在包含空白时检测单词是否有效

时间:2014-02-12 13:29:37

标签: java regex string

我正在开发一款基于手机的文字游戏,玩家可以选择使用相当多的空白(代表任何字母)。

我将所有可能的单词存储在一个hashSet中,因此当一个单词有一个空白时检测单词是否有效只是循环使用字母表替换空白字母并测试单词。我有一个递归调用,所以这将适用于任意数量的空白。代码如下:

public boolean isValidWord(String word) {
    if (word.contains(" ")){
        for (char i = 'A'; i <= 'Z'; i++) {
            if (isValidWord(word.replaceFirst(" ", Character.toString(i))))
                return true;
        }

        return false;
    }
    else
        return wordHashSet.contains(word);
}

随着空白数量的增加,我们必须测试的单词数呈指数增长。当我们达到3个空白时,我们必须先做17576次查找才能拒绝一个单词,这会影响游戏玩法。一旦有4个空白,游戏就会冻结一段时间。

检查多个空白字词的最有效方法是什么。我应该遍历hashset并检查我们是否匹配每个单词?如果是这样,那么我最快的方式是比较考虑空白的两个字符串?我尝试使用正则表达式和String.matches(xx)来做这个,但它太慢了。一个直的String.equals(xx)足够快,但显然不考虑空格。

5 个答案:

答案 0 :(得分:3)

一个非常快速的方法,通过实施有点挑战,将你的单词存储在特里 - http://en.wikipedia.org/wiki/Trie

trie是一个树结构,每个节点都包含一个char,指向下一个节点的指针数组。

没有空格就很容易 - 只需遵循特里结构,就可以在线性时间内检查。当您有空白时,您将有一个循环来搜索所有可能的路线。

如果您不熟悉尝试,这听起来既复杂又困难,但如果您遇到困难,我可以帮您解决一些问题。

编辑:

好的,这里有一些使用try的问题的c#代码,我认为在JAVA中转换它没有问题。如果你这样做,请发表评论,我会帮忙。

Trie.cs

public class Trie
{

    private char blank = '_';

    public Node Root { get; set; }

    public void Insert(String key)
    {
        Root = Insert(Root, key, 0);
    }

    public bool Contains(String key)
    {
        Node x = Find(Root, key, 0);
        return x != null && x.NullNode;
    }

    private Node Find(Node x, String key, int d)
    { // Return value associated with key in the subtrie rooted at x.
        if (x == null)
            return null;

        if (d == key.Length)
        {
            if (x.NullNode)
              return x;
            else
              return null;
        }

        char c = key[d]; // Use dth key char to identify subtrie.

        if (c == blank)
        {
            foreach (var child in x.Children)
            {
                var node = Find(child, key, d + 1);
                if (node != null)
                    return node;
            }

            return null;
        }
        else
            return Find(x.Children[c], key, d + 1);
    }

    private Node Insert(Node x, String key, int d)
    { // Change value associated with key if in subtrie rooted at x.
        if (x == null) x = new Node();
        if (d == key.Length)
        {
            x.NullNode = true;
            return x;
        }

        char c = key[d]; // Use dth key char to identify subtrie.
        x.Children[c] = Insert(x.Children[c], key, d + 1);
        return x;
    }

    public IEnumerable<String> GetAllKeys()
    {
        return GetKeysWithPrefix("");
    }
    public IEnumerable<String> GetKeysWithPrefix(String pre)
    {
        Queue<String> q = new Queue<String>();
        Collect(Find(Root, pre, 0), pre, q);
        return q;
    }

    private void Collect(Node x, String pre, Queue<String> q)
    {
        if (x == null) return;
        if (x.NullNode) q.Enqueue(pre);
        for (int c = 0; c < 256; c++)
            Collect(x.Children[c], pre + ((char)c), q);
    }
}

Node.cs

public class Node
{
    public bool NullNode { get; set; }

    public Node[] Children { get; set; }

    public Node()
    {
        NullNode = false;
        Children = new Node[256];
    }
}

样本用法:

Trie tr = new Trie();
tr.Insert("telephone");
while (true)
{
     string str = Console.ReadLine();
     if( tr.Contains( str ) )
         Console.WriteLine("contains!");
     else
         Console.WriteLine("does not contain!");
}

答案 1 :(得分:1)

  

一个直的String.equals(xx)足够快,但很明显   不考虑空白。

所以我建议实施这个非常接近String.equals()的简单解决方案,并考虑空白:

public boolean isValidWord(String word) {
    if (wordHashSet.contains(word)) {
        return true;
    }
    for (String fromHashSet: wordHashSet){
        if (compareIgnoreBlanks(fromHashSet, word)) {
            return true;
        }
    }
    return false;
}

/**
 * Inspired by String.compareTo(String). Compares two String's, ignoring blanks in the String given as
 * second argument.
 * 
 * @param s1
 *            String from the HashSet
 * @param s2
 *            String with potential blanks
 * @return true if s1 and s2 match, false otherwise
 */
public static boolean compareIgnoreBlanks(String s1, String s2) {
    int len = s1.length();
    if (len != s2.length()) {
        return false;
    }

    int k = 0;
    while (k < len) {
        char c1 = s1.charAt(k);
        char c2 = s2.charAt(k);
        if (c2 != ' ' && c1 != c2) {
            return false;
        }
        k++;
    }
    return true;
}      

答案 2 :(得分:0)

一种丑陋,但我想相当快的方法是创建一个包含所有有效单词的字符串,如下所示:

WORD1
WORD2
WORD3

然后使用像(^|\n)A[A-Z]PL[A-Z]\n这样的正则表达式(即用[A-Z]替换所有空格),并在该字符串上匹配它。

答案 3 :(得分:0)

public boolean isValidWord(String word) {
    word = word.replaceAll(" ", "[a-z]");
    Pattern pattern = Pattern.compile(word);

    for (String wordFromHashSet: hashSet){
        Matcher matcher = pattern.matcher(wordFromHashSet);
        if (matcher.matches()) return true;
    }

    return false;
}

答案 4 :(得分:0)

public boolean isValidWord(String word) {
    ArrayList<Integer> pos = new ArrayList<Integer>();
    for (int i=0; i!=word.length();i++){
        if (word.charAt(i) == ' ') pos.add(i);
    }

    for (String hashSetWord: hashSet){
        for (Integer i: pos){
            hashSetWord = hashSetWord.substring(0,i)+" "+hashSetWord.substring(i+1);            
        }
        if (hashSetWord.equals(word)) return true;
    }
    return false;
}