【问题标题】:Find a complex element in a set of elements在一组元素中找到一个复杂元素
【发布时间】:2021-02-04 03:00:03
【问题描述】:

我有一个函数可以让我找到一个不完整元素与集合中至少一个元素之间的匹配项。 22.2.X.13 是一个不完整元素的示例,其中有一个项目(用 X 定义)可以采用任何值。

此函数的目标是在一组元素中找到至少一个元素,其中第一个位置有 22 个,第二个位置有 2 个,第四个位置有 13 个。

例如,如果我们考虑集合:

{
    20.8.31.13,
    32.3.29.13, 
    24.2.12.13, 
    19.2.37.13, 
    22.2.22.13, 
    27.17.22.13, 
    26.22.32.13, 
    22.3.22.13, 
    20.19.12.13, 
    17.4.37.13, 
    31.8.34.13
} 

函数的输出返回 True,因为有元素 22.2.22.13 对应于 22.2.X.13

我的函数将每对元素(如字符串)和每个元素项作为整数进行比较:

public boolean containsElement(String element) {
    StringTokenizer strow = null, st = null;
    boolean check = true;
    String nextrow = "", next = "";
    
    for(String row : setOfElements) {
        strow = new StringTokenizer(row, ".");
        st = new StringTokenizer(element, ".");
        
        check = true;
        while(st.hasMoreTokens()) {
            next = st.nextToken();
            if(!strow.hasMoreTokens()) {
                break;
            }
            nextrow = strow.nextToken();
            if(next.compareTo("X") != 0) {
                int x = Integer.parseInt(next);
                int y = Integer.parseInt(nextrow);
                if(x != y) {
                    check = false;
                    break;
                }
            }
        }
        if(check) return true;
    }
    return false;

但是,这是一项昂贵的操作,尤其是在字符串大小增加的情况下。您能否建议我另一种策略或数据结构来快速执行此操作?

我的解决方案与字符串密切相关。但是,我们可以考虑其他类型的元素(例如数组、列表、树节点等)

感谢大家的回答。几乎所有的功能我都试过了,板凳:

myFunction: 0ms
hasMatch: 2ms
Stream API: 5ms
isIPMatch; 2ms

我认为正则表达式的主要问题是创建模式和匹配字符串的时间。

【问题讨论】:

  • 似乎是正则表达式的完美应用。
  • 将模板中的每个X替换为\d+,然后使用正则表达式查找匹配的条目。然而,虽然这更简单(并且可能更快一点),但它并没有真正降低问题的复杂性。相反,您可能会考虑使用嵌套 Map 来存储条目,例如{22: {2: {22: {...}}, 3: {...}}, ...}
  • @tobias_k 感谢您的回答。使用地图的问题是“跳过”匹配“X”的元素。
  • 时间差异太接近了,无法考虑一种方法比其他方法好得多 - 噪声很容易解释两次运行之间的几毫秒差异。再匹配几百万个 IP 会产生更准确的结果。

标签: java string data-structures string-matching


【解决方案1】:

其他答案已经讨论过使用正则表达式通过转换例如22.2.X.1322\.2\.\d+\.13 (不要忘记同时转义 . 或者它们的意思是“任何东西”)。但是,虽然这肯定会更简单,也可能会更快一些,但它并没有降低整体复杂性。您仍然需要检查集合中的每个元素。

相反,您可以尝试以这种形式将您的 IP 集转换为嵌套的 Map

{20: {8: {31: {13: null}}, 19: {12: {13: null}}}, 22: {2: {...}, 3: {...}}, ...}

(当然,您应该只创建一次此结构,而不是为每个搜索查询创建一次。)

然后您可以编写一个递归函数match,其工作原理大致如下(伪代码):

boolean match(ip: String, map: Map<String, Map<...>>) {
    if (ip.empty) return true // done
    first, rest = ip.splitfirst
    if (first == "X") {
        return map.values().any(submap -> match(rest, submap))
    } else {
        return first in map && match(rest, map[first])
    }
}

这应该将复杂度从 O(n) 降低到 O(log n);不仅如此,您必须更频繁地分支,但对于 X.X.X.123 最多 O(n) (X.X.X.X 又是微不足道的)。对于小集合,正则表达式可能仍然更快,因为它的开销更少,但对于更大的集合,这应该更快。

【讨论】:

    【解决方案2】:

    您可以按照 Nikolas Charalambidis (+1) 的建议,以基于正则表达式的方式解决此问题,或者您可以采取不同的方式。为避免重复使用另一个答案,我将在这里重点介绍另一种方法,使用 split 方法。

    public boolean isIPMatch(String pattern[], String input[]) {
        if ((pattern == null) || (input == null) || (pattern.length <> input.length)) return false; //edge cases
        for (int index = 0; index < pattern.length; index++) {
            if ((!pattern[index].equals("X")) && (!pattern[index].equals(input[index]))) return false; //difference
        }
        return true; //everything matched
    }
    

    您可以在循环中调用上述方法,然后通过split 将要比较的项目与String 数组进行比较。

    【讨论】:

      【解决方案3】:

      对于字符串,正则表达式更好地解决了这个任务:

      private boolean hasMatch(String[] haystack, String partial) {
          String patternString = partial.replace("X", "[0-9]+").replace(".", "\\.");
          // "22.2.X.13" becomes "22\\.2\\.[0-9]+\\.13" 
          Pattern p = Pattern.compile(patternString);
          for (String s : haystack) {
              if (p.matcher(s).matches()) return true;
          }
          return false;
      }
      

      对于其他类型的对象,这取决于它们的结构。

      • 如果有某种顺序,您可以考虑让您的元素实现Comparable - 然后您可以将它们放入TreeSet(或作为TreeMap 中的键),这将始终保持排序.这样,您可以只比较可以匹配的元素:mySortedSet.subSet(fromElement, toElement) 只返回这两者之间的元素。
      • 如果没有顺序,您只需将所有元素与您的“模式”进行比较。

      请注意,字符串可比较的,但它们的默认排序顺序忽略了.-分隔符的特殊语义。因此,您可以小心地实施基于树集的方法,以使搜索优于线性搜索。

      【讨论】:

        【解决方案4】:

        您想使用专门为此类任务制作的正则表达式。查看demo

        22\.2\.\d+\.13
        

        Java 8 及更高版本

        从 Java 8 开始,您可以使用 Stream API 来找到至少一个使用 PatternMatcher 类的正则表达式匹配:

        Set<String> set = ... // the set of Strings (can be any collection)
        
        Pattern pattern = Pattern.compile("22\\.2\\.\\d+\\.13"); // compiled Pattern
        boolean matches = set.stream()                           // Stream<String>
                             .map(pattern::matcher)              // Stream<Matcher>
                             .anyMatch(Matcher::matches);        // true if at least one matches
        

        Java 7 及更低版本

        方式等同于 Stream API:一个短路 for-each 循环,带有 break 语句,以防找到匹配项。

        boolean matches = false;
                
        Pattern pattern = Pattern.compile("22\\.2\\.\\d+\\.13");
        for (String str: set) {
            Matcher matcher = pattern.matcher(str);
            if (matcher.matches()) {
                matches = true;
                break;
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2014-10-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-02-02
          • 1970-01-01
          相关资源
          最近更新 更多