【问题标题】:Regex - catch repeating numbers (not digits)正则表达式 - 捕获重复数字(不是数字)
【发布时间】:2012-11-12 17:44:33
【问题描述】:

我正在尝试用 Java 编写一个方法来检查字符串并允许它只包含数字和逗号。此外,不能有重复的数字。

例如:

  • 11,22,33 - 没关系
  • 22,22,33 - 这不行

我已经使用正则表达式和Set<String>(如下)的组合完成了它的初稿,但正在寻找更好的东西,最好只使用正则表达式。

public boolean isStringOk(String codes) {
    if(codes.matches("^[0-9,]+$")){ 
        Set<String> nonRepeatingCodes = new LinkedHashSet<String>();
        for(String c: codigoRoletas.split(",")){
            if(nonRepeatingCodes.contains(c)){
                return false;
            }
            else{
                nonRepeatingCodes.add(c);
            }
        }
        return true;
     }
    return false;
}

有谁知道这是否可能只使用正则表达式?

【问题讨论】:

  • 只使用正则表达式是不可能的。正则表达式不会跟踪所有匹配的实际数字。它只知道digit 匹配。对于几场比赛,你仍然可以做到。但是如果你不知道字符串的长度,那就是i don't know how much complex, even if its possible
  • 使用正则表达式可能是可能的,但对于下一个出现的人来说,这将是完全神秘的。 Jamie Zawinski 之所以对正则表达式发表评论,就是出于这样的原因。
  • 值的子范围是否总是相同的长度?例如,11,22,33 表示长度为 2,111,222,333 表示长度为 3,等等
  • @RohitJain is 只能使用正则表达式。但我完全同意 Jarrod Roberson 的观点!
  • @m.buettner.. 是的,我看到了你的解决方案,这就是我指出的原因,它可能很复杂,就像你的回答一样。

标签: java regex numbers repeat


【解决方案1】:

我怀疑这是可取的(正如 Jarrod Roberson 所提到的),因为对于您项目的任何其他编码人员来说,这都很难理解。但绝对可以只使用正则表达式:

^(?:(\d+)(?!.*,\1(?!\d)),)*\d+$

双重否定的前瞻使其有点难以理解。但这里有一个解释:

^                # anchor the regex to the beginning of the string
(?:              # subpattern that matches all numbers, but the last one and all commas
    (\d+)        # capturing group \1, a full number
    (?!          # negative lookahead, that asserts that this number does not occur again
        .*       # consume as much as you want, to look through the whole string
        ,        # match a comma
        \1       # match the number we have already found
        (?!\d)   # make sure that the number has ended (so we don't get false negatives)
    )            # end of lookahead
    ,            # match the comma
)*               # end of subpattern, repeat 0 or more times
\d+              # match the last number
$                # anchor the regex to the beginning of the string

请注意,这只是一般的正则表达式,而不是特定于 Java。在 Java 中,您需要转义每个反斜杠,否则它将无法通过正则表达式引擎:

^(?:(\\d+)(?!.*,\\1(?!\\d)),)*\\d+$

【讨论】:

    【解决方案2】:

    请注意,将正则表达式用于技术上的非正则语言可能很危险,尤其是对于大的、不匹配的字符串。如果你不小心,你可以引入指数时间复杂度。此外,正则表达式引擎必须执行一些后门技巧,这些技巧也会降低引擎的速度。

    如果您尝试其他解决方案并且它们给您带来问题,您可以使用捕获组以及PatternMatcher 类以使您的代码更清晰:

    private static final Pattern PATTERN = Pattern.compile("([\\d]+),?");
    
    public static boolean isValid(String str) {
        Matcher matcher = PATTERN.matcher(str);
        Set<Integer> found = new HashSet<Integer>();
        while (matcher.find()) {
            if (!found.add(Integer.parseInt(matcher.group(1)))
                return false;
        }
        return true;
    }
    

    【讨论】:

    • +1 用于指出常规语言和其他(上下文相关)语言之间存在差异。
    • 为什么直接使用PatternMatcher 类?我相信String.split() 将获得相同的结果,即更少的代码和资源占用。 - 为什么要转换为Integer?我认为它降低了灵活性并由于转换操作而增加了不必要的处理,而就 OP 的示例而言,boolean 结果没有真正的用途。 (如果必须保证整数格式,我意识到匹配器和这种转换很重要)--- 请参阅my version of your method,如果您愿意,可以随意使用。
    • @TheLima 您的所有回答都是重申 OP 已经知道的内容。他的问题中已经有了String.split(",") 解决方案。至于额外的分配/处理,你说的是微秒和千字节,这意味着过早的优化。我的代码有意义且可读,即what experts do。他想要一些匹配数字的东西,所以我让它匹配数字。这是故意不灵活的,因为代码应该表明目的。
    • 我真的不认为你的观点是有效的。您的代码与String.split() 的区别与“Half a 打”“6” 之间的区别一样好。里面有不必要的代码,通过增加噪音降低清晰度。而且,没关系代码,虽然性能变化确实在对示例进行了过早的优化,但如果将代码应用于大量数据,那么毫秒和千字节的重要性就非同小可了;考虑到问题的性质,这不足为奇。 --- 我看不出任何真正的原因,这些变化会变得更糟而不是更好。
    • 您所说的海量数据无效。如果他想首先使用正则表达式,那么就不会有“大量数据”,因为正则表达式本质上会比大多数其他验证方法(如testing primality)慢。更不用说即使使用 your 方法也有大数据的内存问题。他要求使用split 的替代解决方案,所以我给了他一个简洁明了的解决方案,而不是告诉他他已经知道的内容。
    【解决方案3】:

    这是我能想到的最不难看的正则表达式:

    return codes.matches("^(?:,?(\\d+)(?=(?:,(?!\\1\\b)\\d+)*$))+$");
    

    细分:

    • ,? 使用下一个逗号(如果有的话)(即它不是字符串的开头)。

    • (\d+) 捕获组 #1 中的下一个数字

    • (?=(?:,(?!\1\b)\d+)*$) 尝试匹配剩余的数字,检查每个数字以确保它与刚刚捕获的数字不同。

    反向引用后的\b 可以防止像11,111 这样的字符串出现误报。其他任何地方都不需要它,但如果需要,您可以在每个\d+ 上添加一个,这可能会使正则表达式更高效。但是,如果您需要调整正则表达式以获得最佳性能,则使所有量词都具有所有格会产生更多效果:

    "^(?:,?+(\\d++)(?=(?:,(?!\\1\\b)\\d++)*+$))++$"
    

    【讨论】:

      【解决方案4】:

      这个正则表达式会做

      ^(?=^\d+(?:,\d+)*$)(?!^.*?((?<=(?:^|,))\d+(?=[,$])).*?\1(?:$|,.*?)).*?$
      

      (?=^\d+(?:,\d+)*$) 检查有效格式,例如 45 或 55,66,88,33

      (?!^.*?((?&lt;=(?:^|,))\d+(?=[,$])).*?\1(?:$|,.*?)) 不匹配,如果有任何重复的数字..

      .*? 匹配所有内容,前提是上述负前瞻返回 true

      作品here

      【讨论】:

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