【问题标题】:Regex to match special characters EXCEPT hyphen(s) mixed with number(s)正则表达式匹配特殊字符,除了连字符与数字混合
【发布时间】:2013-01-18 16:44:23
【问题描述】:

我们目前在 Java 的 replaceAll 函数中使用 [^a-zA-Z0-9] 从字符串中去除特殊字符。我们注意到,当连字符与数字混合时,我们需要允许它们。

连字符不会匹配的示例:

  • 1-2-3
  • -1-23-4562
  • --1---2--3---4-
  • --9--a--7
  • 425-12-3456

匹配连字符的示例:

  • --a--b--c
  • 沃尔玛

我们认为我们使用 this SO question 作为参考制定了一个正则表达式来满足后一个标准,但我们不知道如何将它与原始正则表达式 [^a-zA-Z0-9] 结合起来。

由于Lucene's standard tokenizer 在索引时的工作方式,我们希望对 Lucene 搜索字符串执行此操作:

在连字符处拆分单词,除非标记中有数字,在这种情况下,整个标记被解释为产品编号并且不分割。

【问题讨论】:

  • 我根据正则表达式标签的文档添加了 [java] 标签:“请同时包含一个标签,指定您正在使用的编程语言或工具。”
  • 连字符只能与数字结合使用?所以 '--9--a--7' 会去掉连字符,但会留下 '--9--0--7' 吗?
  • 我相信这两个例子都会被搁置,因为 Lucene 引用指出,如果令牌中有数字,分词器不会拆分连字符。
  • 所以 '--9--a--7' 和 '--9--0--7' 会被单独留下,而不是 '--a--b--c' ?
  • 是的,完全正确。

标签: java regex


【解决方案1】:

你不能用一个正则表达式来做到这一点。 (嗯...也许在 Perl 中。)

(edit:好的,您可以使用可变长度的负向后视来做到这一点,Java 似乎可以(几乎是独一无二的!)做到这一点;请参阅 Cyborgx37 的答案。无论如何,imo,你 不应该使用单个正则表达式来做到这一点。:))

可以做的是将字符串拆分为单词并单独处理每个单词。我的 Java 非常糟糕,所以这里有一些有希望的 Python:

# Precompile some regex
looks_like_product_number = re.compile(r'\A[-0-9]+\Z')
not_wordlike = re.compile(r'[^a-zA-Z0-9]')
not_wordlike_or_hyphen = re.compile(r'[^-a-zA-Z0-9]')

# Split on anything that's not a letter, number, or hyphen -- BUT dots
# must be followed by whitespace
words = re.split(r'(?:[^-.a-zA-Z0-9]|[.]\s)+', string)

stripped_words = []
for word in words:
    if '-' in word and not looks_like_product_number.match(word):
        stripped_word = not_wordlike.sub('', word)
    else:
        # Product number; allow dashes
        stripped_word = not_wordlike_or_hyphen.sub('', word)

    stripped_words.append(stripped_word)

pass_to_lucene(' '.join(stripped_words))

当我使用'wal-mart 1-2-3' 运行它时,我会返回'walmart 1-2-3'

但老实说,上面的代码重现了 Lucene 分词器已经在做的大部分事情。我认为您最好将StandardTokenizer 复制到您自己的项目中并对其进行修改以执行您想要的操作。

【讨论】:

  • 可以,但不漂亮。
【解决方案2】:

你试过了吗:

[^a-zA-Z0-9-]

【讨论】:

  • 我不知道这会起作用 - OP 似乎建议连字符仅在仅与数字组合时才允许使用。不过不清楚。
  • OP 提供了一些附加信息 - 连字符仅在与数字组合时才允许使用。如果没有数字,则仍然必须删除连字符。
  • @Cyborgx37:谢谢,我会看看并修改我的答案。
【解决方案3】:

这个问题很棘手,因为 Java 不允许在环视中进行无限递归,这基本上是您所需要的。正如您将看到的,我已经设置了 100 个字符的限制,如果您希望单词更长,可以增加。

这应该可行:

(?<![0-9]\S{0,100})[^a-zA-Z](?!\S{0,100}[0-9])|(?<=[0-9]\S{0,100})[^a-zA-Z0-9-](?=\S{0,100}[0-9])

只需一个带有此表达式的简单 replaceAll() 即可处理它。

例如,考虑这个输入:

--9-+-a--7 wal-mart

上面的表达式,其中有问题的字符被替换为长度为零的字符串,将呈现以下输出:

--9--a--7 walmart

您可以在这里试用:http://fiddle.re/ynyu

请注意,此表达式取决于由空格分隔的单词(空格、制表符、换行符等)。其他字符,例如逗号和分号,将导致表达式将这两个单词视为一个单词。例如 '---9-a-0-,wal-mart' 将被视为一个单词。

编辑 我之前编辑的最后一段不正确。如果您想包含其他字符作为分隔符,我建议在第一遍中将它们替换为空格(例如,将 ',' 替换为 ' ')。

我主要是一个 .NET 程序员,否则我会给你完整的 Java 代码来使用这个模式。

【讨论】:

  • 啊哈,聪明。似乎 Java 拥有为数不多的允许在 (?&lt;!...) 中不只是固定长度模式的正则表达式引擎之一;这当然使它变得不那么不可能:)
  • @Eevee - 是的,根据this 没有很多。 .NET 允许无限递归,所以我发现我经常不得不简化我的正则表达式才能在其他风格中工作。
  • 令人印象深刻。甚至 Perl,这种精神错乱的鼻祖,也从未实现过任意可变宽度的负向回溯。不过,我很少遇到限制;你到底在做什么,你“经常”打它:)
  • @Eevee - .NET 还使用了其他表达式,例如基于命名捕获执行 if-else 表达式的能力,这在此类场景中非常有用。
  • @Eevee - 当您在后视中进行前瞻时,这真的很疯狂。 .NET 对您在环视中放置的内容没有任何限制。
【解决方案4】:

请原谅我发布第二个答案而不是编辑第一个答案,但我不完全确定问题是在它们立即被字母包围的情况下消除破折号,还是仅在根本不包含数字的字符串。该解决方案适用于后一种情况。我的另一个解决方案是针对前一种情况。

这种模式

String newValue = myString.replaceAll("[^\\sA-Za-z0-9\\-]|((?<!\\S*\\d)-(?!\\S*\\d))", "");

应该这样做。有两个主要部分与or 相连。第一部分匹配所有非字母、非数字、非破折号的字符,因为无论如何我们都想去掉这些字符。 or 的后半部分将匹配任何在令牌之前没有数字的破折号,并且在令牌中没有数字之后(即,令牌中根本没有数字,其中令牌由所有非空格或\S,字符)。这是通过消极的后视和前瞻来完成的。我们确实利用了 Java 在这些前瞻/后视中支持可变宽度这一事实。当然替换的只是空字符串。

我不得不承认,虽然在 Java 中使用正则表达式的语法很痛苦(在你必须使用 Pattern.compile 等的情况下),但至少引擎支持一些不错的特性。尽管根据 Eevee 的说法,它可能不如 .NET 好。

不过,我同意其他人的观点,因为这并不是您通常想要在单个正则表达式中执行的操作。我不知道你的确切情况,但是一个简单的分支来检测它是否看起来是一个产品编号,然后应用正确的模式会更具可读性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-03
    • 1970-01-01
    • 2020-12-28
    • 1970-01-01
    • 1970-01-01
    • 2022-08-17
    • 2023-03-11
    相关资源
    最近更新 更多