在字符串集中搜索字符串排列

时间:2009-09-15 16:52:18

标签: java algorithm string search data-structures

这个标题有点尴尬;我真的不确定如何总结这一点。我知道如何做到这一点,我只是不确定如何有效地做到这一点。这是我的问题:

我有一个字符串作为输入。让我们说:

  

foo bar

我有一大堆字符串(成千上万)。让我们说:

  

foo,baz,bar,blah,foo bar,foo baz

我需要将输入与集合中的字符串进行匹配。在这种情况下,“foo”,“bar”和“foo bar”被认为是匹配。

所以,我需要以某种方式搜索输入的所有排列(它可能超过2个单词),或以某种方式检测用户是否打算将它(或其中的一部分)放在引号中。或者做一些我没有想到的事情。

我可以使用某种数据结构或算法吗?我该怎么做呢,或者我不应该处理这个用例?

编辑:上面有一个错字,扭曲了问题;在上面的例子中,“foo baz”也是一个匹配。对于那个很抱歉。我基本上想要将输入单词的任何排列与字典匹配。因此,“abc xyz”的输入将匹配“123 abc”或“abc xyz”或“xyz 123”,但不匹配“abcxyz”。

7 个答案:

答案 0 :(得分:2)

我建议使用字典。使用字符串作为键和字符串列表作为值。对要搜索的字符串进行标记,并为每个标记将整个字符串添加到字典中一次。 (你可以使用split方法来标记你的字符串。使用空格作为分隔符。)此后,无论何时你需要进行查找,你都会对搜索字符串进行标记并对字典中的每个标记进行查找。

因此,如果您添加了以下字符串:foo,baz,bar,blah,foo bar,foo baz

你的词典有条目:

foo:foo,foo bar,foo baz 巴兹:巴兹,富巴兹 酒吧:酒吧,foo酒吧 哇:等等

然后你应该搜索“foo bar”,

你的输出是存储在foo和bar下的条目的并集,如下所示: “foo bar”:= foo,bar

foo:foo,foo bar,foo baz 联盟 bar:bar,foo bar

给予:foo,foo bar,foo baz,bar

编辑:我刚刚注意到你只想要完全或部分匹配,即foo baz是不可接受的。简单的解决方案是对结果进行后处理 - 将搜索字符串和目标字符串的较长时间限制为较短的长度,然后将截断的字符串与未修改的字符串进行比较。只接受那些相同的东西。

编辑:事实证明,foo baz确实是一场比赛。忽略上面的段落(第一次编辑)。 请参阅(C#)代码,如下所示:

class DictionarySearch
{
    private Dictionary<string, List<string>> dict;

    public DictionarySearch()
    {
        dict = new Dictionary<string, List<string>>();
    }

    /// <summary>
    /// Add a string e.g. foo bar to the dictionary
    /// </summary>
    /// <param name="s">string to be added</param>
    public void addString(string s)
    {
        //tokenize string
        string[] words = s.Split(new char[] { ' ' });

        //add each token to the dictionary as a key with the matching value being s
        foreach (string w in words)
        {
            if (dict.ContainsKey(w))
            {
                dict[w].Add(s);
            }
            else
            {
                dict.Add(w, new List<string>());
                dict[w].Add(s);
            }
        }
    }
    /// <summary>
    /// Find all strings which match at least one token
    /// </summary>
    /// <param name="s">string of tokens (words) to be matched</param>
    /// <returns>List of strings matching at least one word</returns>
    public IList<string> getMatches(string s)
    {
        //split search string into words
        string[] words = s.Split(new char[] { ' ' });
        List<string> output = new List<string>();

        //retrieve from dictionary list of strings matching each word.
        foreach (string w in words)
        {
            if (dict.ContainsKey(w))
            {
                output.AddRange(dict[w]);
            }
            else
            {
                continue;
            }
        }

        return output;
    }
}

给定一个字典,其中包含m个字符串,每个字符串包含q个单词,n个唯一字,以及包含l个单词的搜索字符串,时间复杂度如下:

填充数据结构:O(q m T [字典插入])。需要为每个单词执行插入

查找字符串:O(l * T [字典 - 查找])。搜索字符串中每个单词的字典查找。

实际费用取决于您的字典实施。基于哈希表的字典对插入和查找都会产生O(1)成本。基于二叉树的字典会导致插入和查找的成本为O(lg n)。

答案 1 :(得分:2)

你的字典有多大?您可以将字典转换为trie。有人发布如何将字典转换为特里的帖子。一旦你这样做,查找就简单而快速。

此外,一个简单的解决方案可能是将搜索字符串分解为单独的单词,并在您的trie中搜索每个单词,确保重复项不被视为两次。

答案 2 :(得分:1)

您需要的是Lucene

答案 3 :(得分:1)

(当你说“有效”时,你可能需要在空间和时间方面更明确。让我们假设你的意思是时间效率(假设你提到了排列)。

计算

答案的任务
String[] findStringsContaining(List<String> strings, String[] words)

可以被分区并传递给并行执行线程,因为它在中间阶段是纯函数和副作用,并且结果作为最后一步加入。即您可以对单词和/或字符串列表进行分区。

这就是map-reduce的工作原理(和你的情况一样,它与所有在同一台机器上发生的事情无关。)

您的映射器(分配给每个单词的主题)是:

boolean [] stringContainsWord (List<String> strings, String word);

该方法将并行执行。

对于给定单词匹配的每个索引(List),布尔数组的值为true。

和你的reducer(在所有映射器完成后运行)是:

List<String> getMatchingList(List<String>, List<boolean[]> mapperResults);

不考虑线程的开销,并假设映射器线程的成本可以忽略不计,对于合理数量的输入字,这会给你一个O(n)(对于映射器)+ O(m)(对于reducer)时间过程,其中n是字符串列表中的项目数,m是输入中的单词数。

您可以通过对字符串列表进行分区并为每个单词运行p个线程,并让每个线程搜索字符串列表的子集来进一步并行化任务,以便映射器的输入列表为1 / p整体列表中的元素。

-

您可能需要考虑的另一种方法,特别是如果字符串列表很大,并且内容是langauge(例如英语),则考虑到大多数语言都有相当少的单词这一事实进行优化构成该语言中的大部分句子。例如,如果您的列表有200万个英语句子,那么唯一单词列表可能会小许多个数量级(比如几百个)。

在这种情况下,你可以有一个单词地图 - &gt;句子,并测试任何给定单词的匹配句子将减少到地图中的查找。

(请注意,您仍然可以将初始方法与此结合使用。)

答案 4 :(得分:1)

对于包含多个单词短语的大型输入字符串和词典,请考虑Rabin-KarpAho-Corasick algos。

(链接到Rabin-Karp - http://en.wikipedia.org/wiki/Rabin - Karp_string_search_algorithm - 由于某些原因我无法将其超链接上面的参考文献)

答案 5 :(得分:0)

此代码有效。不知道这对你来说是否足够有效:

    String[] dict = "foo bar".split(" ");

    String[] array = new String[] { "foo", "baz", "bar", "blah", "foo bar",
            "foo baz" };

    loop: for (String s : array) {
        String[] a = s.split(" ");

        for (String sample : dict)
            for (String s1 : a)
                if (sample.equals(s1)) {
                    System.out.println(s);
                    continue loop;
                }
    }

答案 6 :(得分:0)

来自ejspencer的想法我把它放在一起

// Build the dictionary/data structure 
// O( [average split length]*n )
public static Dictionary<String,List<int>> BuildDictionary(String[] data)
{
    String[] temp;
    Dictionary<String,List<int>> dict = new Dictionary<String,List<int>>();
    for(int i = 0; i < data.length; i++)
    {
        temp = data[i].split(" ");
        for(int j = 0; j < temp.length; j ++)
        {
            if(dict.get(temp[j]) == null)
                dict.put(temp[j],new List<int>());

            dict.get(temp[j]).add(i);
        }
    }

    return dict;
}

// find all the matches
// O( [average number of matches per key]*[input split length])
public static List<int> FindMatches(String input, Dictionary<String,List<int> dict)
{
    String[] temp = input.split(" ");
    List<int> ret = new List<int>();

    for(int i = 0; i < temp.length; i++)
    {
        if(dict.get(temp[i]) == null)
            continue; // no match

        // read the match into the return list, ignore copies
        List<int> match = dict.get(temp[i]);
        for(int j = 0; j < match.count(); j++)
            if(!ret.contains(match.get(i))
                ret.add(match.get(i));
    }

    return ret;
}

它可能不会立即编译,但我认为你无论如何都要使用它,这给你一个非常好的想法,快速访问和简单的代码(没有进攻alphazero)。

此搜索区分大小写,但您可以像上午或更低版本一样使用它来更改它。