【问题标题】:Search For String Permutations In String Set在字符串集中搜索字符串排列
【发布时间】:2010-11-28 12:56:23
【问题描述】:

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

我有一个字符串作为输入。比方说:

富吧

而且我有一组非常大的字符串(数万个)。比方说:

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

我需要将输入与集合中的字符串相匹配。在这种情况下,“foo”、“bar”和“foo bar”被视为匹配项。

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

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

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

【问题讨论】:

  • 字符串是英文单词还是可以包含任何字符?它们区分大小写吗?
  • @Adamski 它们是英文单词,不区分大小写;但是,它们是非常专业的词,就像您在字典中找不到的东西一样。
  • 如果字典包含“foob”,如果我搜索“foo”会返回,还是您只关心完全匹配?
  • @Adamski 查看我所做的编辑。我只关心有效单词的精确匹配,即 foo 将匹配 "foo bar" 或 "bar foo" 但不匹配 "foob"。

标签: java algorithm string search data-structures


【解决方案1】:

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

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

您的字典有条目:

foo: foo, foo bar, foo baz 巴兹:巴兹,富巴兹 酒吧:酒吧,富酒吧 废话:废话

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

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

foo: foo, foo bar, foo baz 联盟 酒吧:酒吧,富酒吧

给予: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(qmT[dictionary-insert])。需要对每个单词进行一次插入

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

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

【讨论】:

    【解决方案2】:

    对于包含多词短语的大型输入字符串和字典,请考虑使用 Rabin-Karp 或 Aho-Corasick 算法。

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        你需要的是Lucene

        【讨论】:

        【解决方案5】:

        来自 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)。

        此搜索区分大小写,但您可以轻松地使用 toUpper 或 toLower 来更改它。

        【讨论】:

          【解决方案6】:

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

          计算答案的任务

          String[] findStringsContaining(List<String> strings, String[] words)
          

          可以被分割并移交给并行执行线程,因为它在中间阶段是纯粹的功能性和无副作用的,并且结果作为最后一步加入。 IE。您可以对单词和/或字符串列表进行分区。

          这就是map-reduce 的工作方式(而你的情况,与这一切都发生在同一台机器上无关。)

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

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

          该方法将并行执行。

          布尔数组对于与给定单词匹配的每个(列表)索引都会有一个真值。

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

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

          撇开线程的开销,并假设对于合理数量的输入字,映射器线程计数的成本可以忽略不计,这将为您提供 O(n)(对于映射器)+O(m)(对于减速器)时间过程,其中 n 是字符串列表中的项目数,m 是输入中的单词数。

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

          --

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

          在这种情况下,您可以拥有一个单词 -> 句子的映射,并且对任何给定单词的匹配句子的测试简化为在映射中查找。

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

          【讨论】:

            【解决方案7】:

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

                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;
                            }
                }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-11-30
              • 2014-10-12
              相关资源
              最近更新 更多