【问题标题】:Regex: find word with extra characters正则表达式:查找带有额外字符的单词
【发布时间】:2017-11-02 16:16:35
【问题描述】:

我想在一个句子中找到“帮助”这个词。这本身就是一件容易的事。然而,在某些情况下,这个词可能写成heelphhelp,基本上包含比通常更多的字符。当然,有些例子比其他例子更真实。

查找“帮助”的基本正则表达式(忽略大小写差异 - (?i) 可以涵盖这一点)是:

(help)

但是,此正则表达式仅检测直截了当的单词,而不考虑可以添加的额外字符。

替换双字符不是一种选择,因为有些单词通常(

那么使用正则表达式,有什么方法可以让我找到以某种方式具有“帮助”的单词吗?

测试文本(说明正则表达式是否应该找到它)

heelp (match)
help (match)
help (match)
heeeelp (match)
hhhheeeelllllpppp (match)
heeeklp (match)
hlep (no match)
helper (no match)
helperp (no match) 
hhhheeeeekklllllpppp (match)
hpeepr33erlrpetertp (no match)
heplp (match)
hepl (no match)
heeeeellllllllpppppppppppl (no match)

数字应该被忽略。

h+e+l+p

(查看边界)将排除实例heplp

至于每种类型的字符数量,它会有所不同。这就是我不能只制作字符串数组的原因。

如果相关的话,我使用的编程语言是 Java。此外,外壳并不重要。如有必要,可以在检查之前将其小写,或者我可以添加不区分大小写标志。


TL:DR; 目标是在中间有其他字符(可能是也可能不是与前面的字符相同)作为检测目标的单词中的字符(同样,在本例中为help)。

【问题讨论】:

  • 查看正则表达式特殊字符*+
  • 你可以使用\\bh+e+l+p+\\b
  • 我假设您的意思是“heeeklp”是“(未找到)”,因为它有一个“k”
  • ^(h+e+l+p+)$ 假设您不想匹配单词heeeklp
  • 如果您的目标真的是如您之前在评论中所说的智能聊天系统,那么这样的正则表达式将毫无用处。首先,许多人错误地排列字母。所以hlep 可能是请求help 的实际尝试。其次,有很多单词包含其他的单词。例如“助手”或“直升机”。也许关于 NLP 的答案实际上是适合您情况的正确答案。

标签: java regex


【解决方案1】:

这不是一件容易的事,你需要一个好的 NLP(自然语言处理)库。

对于可能是 Apache OpenNLP 项目的 Java。

对于 Perl,有像 Lingua::Stem(如果你在 stemming 之后)或 PHP soundex(如果你在类似 语音 词之后)这样的模块。

【讨论】:

  • 为什么?用一个简短的 RegEx 来解决这个任务似乎很简单,至少在我看来是这样的!?
  • 正如xander所说,不用拖入NLP就可以解决。还有一个正则表达式也可以,因此不需要 NLP
【解决方案2】:

这行得通。尝试任何在线正则表达式测试器,以确保它是您正在寻找的: 备注:这适用于任何数量的不需要的字母,如果您需要 1 个字母 - “\w*” 模式应替换为“\w?” (以及相应的 java 代码)

\bh+\w*e+\w*l+\w*p+\b

更新*

这是在任何单词上获取此类正则表达式的 java 代码

public static String getRegExForWord(String word){
        char[] chars =  word.toCharArray();
        StringBuilder pattern = new StringBuilder("\\b");
        for (int i = 0; i < chars.length-1; i++) {
            pattern.append(chars[i]).append("+\\w*");
        }
        return pattern.append(chars[chars.length - 1]).append("+\\b").toString();
    }

【讨论】:

  • 这仅适用于help!您的意思是为每个需要捕获的单词定义一个正则表达式吗?!
【解决方案3】:

更新版本

h = h.trim();
h = h.replaceAll("\\s+", "\n");

Pattern p = Pattern.compile("(h+.*?e+.*?l+.*?p+)", Pattern.MULTILINE);
Matcher m = p.matcher(h);
while(m.find())
{
    System.out.println(m.group(1));
}

【讨论】:

  • 打开了一个 regex101.com 选项卡,其中包含示例文本 (heelp help help heeeelp hhhheeeelllllpppp hlep helper heeklp)。这个正则表达式(就目前而言)对hlep help 作为一组做出反应——即两个错误。 (它不应该对 hlep 做出反应,也不应该将这两者视为一组)
  • 哦,现在我明白了。我以为你会一次接一个字。将其更新为单词仅由空格分隔的字符串:)
  • 有点晚了,我知道,但是在我的代码中,所有的空格都被替换为 '\n',将它们放在新的一行上。
  • 问题中使用的换行格式是这样我可以概述示例输入。现在我认为(不能完全确定)你的答案有一个单词多行检查。
  • 是的。知道了。感谢您的澄清。
【解决方案4】:

我向您提出以下(一般)解决方案:

  1. 压缩每个单词,以免有任何重复的字母
  2. 获取要匹配的单词字典
  3. 匹配字典中具有最小 Levenshtein 距离的单词

压缩应该产生这个:

heelp -> help
help -> help
heeeelp -> help
hhhheeeelllllpppp -> help
heeeklp -> heklp
hlep -> hlep
helper -> helper

两个单词之间的 Levenshtein 距离 (LD(word1, word2)) 是要更改以使它们相等的字符数。示例:

hhhheeeelllllpppp -> help -> LD(help, help) = 0, LD(help, helper) = 2 <- help match
heeeklp -> heklp -> LD(heklp, help) = 1, LD(heklp, helper) = 3 <- help match
hlep -> hlep -> LD(hlep, help) = 2, LD(hlep, helper) = 3 <- help match
helper -> helper -> LD(helper, help) = 2, LD(helper, helper) = 0 <- helper match

这是我的解决方案:

import java.util.*;

public class LevenshteinDistance {                                               
    private static int minimum(int a, int b, int c) {                            
        return Math.min(Math.min(a, b), c);                                      
    }                                                                            

    public static int computeLevenshteinDistance(CharSequence lhs, CharSequence rhs) {      
        int[][] distance = new int[lhs.length() + 1][rhs.length() + 1];        

        for (int i = 0; i <= lhs.length(); i++)                                 
            distance[i][0] = i;                                                  
        for (int j = 1; j <= rhs.length(); j++)                                 
            distance[0][j] = j;                                                  

        for (int i = 1; i <= lhs.length(); i++)                                 
            for (int j = 1; j <= rhs.length(); j++)                             
                distance[i][j] = minimum(                                        
                        distance[i - 1][j] + 1,                                  
                        distance[i][j - 1] + 1,                                  
                        distance[i - 1][j - 1] + ((lhs.charAt(i - 1) == rhs.charAt(j - 1)) ? 0 : 1));

        return distance[lhs.length()][rhs.length()];                           
    }

  public static String compress(String s) {
    char[] chars = s.toCharArray();
    Character last_char = null;

    StringBuilder sb = new StringBuilder();
    for (Character c:chars) {
      if(c != last_char) {
        sb.append(c);
        last_char = c;
      }
    }
    return sb.toString();
  }

    public static void main(String[] argv) {
      String[] strings = {"heelp", "help", "heeeelp", "hhhheeeelllllpppp", "heeeklp", "hlep", "helper"};
      String[] dict = {"help", "helper"};

      String match = "", c;
      int min_distance, distance;
      for(String s : strings) {
        c = compress(s);
        min_distance = computeLevenshteinDistance(c, "");

        for(String d : dict) {
          distance = computeLevenshteinDistance(c, d);
          System.out.println("compressed: "+c+ " dict: "+d+" distance: "+Integer.toString(distance));
          if(distance < min_distance) {
            match = d;
            min_distance = distance;
          }
        }

        System.out.println(s + " matches " + match);
      }
    }                                                                            
}

这是输出:

compressed: help dict: help distance: 0
compressed: help dict: helper distance: 2
heelp matches help
compressed: help dict: help distance: 0
compressed: help dict: helper distance: 2
help matches help
compressed: help dict: help distance: 0
compressed: help dict: helper distance: 2
heeeelp matches help
compressed: help dict: help distance: 0
compressed: help dict: helper distance: 2
hhhheeeelllllpppp matches help
compressed: heklp dict: help distance: 1
compressed: heklp dict: helper distance: 3
heeeklp matches help
compressed: hlep dict: help distance: 2
compressed: hlep dict: helper distance: 3
hlep matches help
compressed: helper dict: help distance: 2
compressed: helper dict: helper distance: 0
helper matches helper

【讨论】:

  • 尽管这可能是一个很好的系统,但这需要您知道可以用这些字母构建的所有不同单词。在help 的情况下,它不是很多,但换句话说,可能还有更多。不过,还有helpinghelped 甚至更多。否则你可能会误报
  • 可能我错过了什么。在您提供的示例中,似乎至少您知道“帮助”和“助手”这两个词,然后您尝试将带有额外字符的词减少到这些词。你说得对,会有误报。 "helping" 可以匹配 "help" 和 "helper"。您可以使用 Levenshtein 距离上的阈值来避免这种情况,包括单词不匹配的可能性。
【解决方案5】:

\bh+\w{0,1}e+\w{0,1}l+\w{0,1}p+\b

在 regex101.com 上针对 javascript 进行了测试,以获得示例输入所需的结果。它比使用“*”“更严格”,它只允许零个或一个杂散字母。这符合我的印象,即您允许任何数字中的正确字母,但错误字母只允许两个正确字母之间的一个。

将匹配“帮助”,每个正确字母的任意数字 (>0) 以正确的顺序排列。 在每两个(组)正确字母之间,允许任何其他“单词”字母(数字、字母、“_”)中的一个或零个。单词必须以第一个正确字母开头,并以最后一个正确字母结尾。

为了更精确地选择正确字母之间允许的字母,您可以使用[alltheallowedletters],以防您不喜欢\w 集合。

我将? 替换为{0,1} 以展示该语法的灵活性。

【讨论】:

  • @LunarWatcher 我希望 heeeklp 匹配,因为它仍然包含“帮助”这个词,即使它有一个不相关的字符根据这个声明像hhhheeeeekklllllpppp 这样的词也应该匹配。不应该吗?
  • 我的印象(现在似乎已经确认)一个错误的字母是可以的,但两个(或一个错误的重复)是不需要的。
  • 是的,它应该(注意到当你写评论时它实际上不起作用)。但是基础在那里,所以通过这样做:\bh+\w*?e+\w*?l+\w*?p+\b(在\w 之后添加*)它可以工作。 Working sample
  • 现在它匹配了很多其他的废话hpeepr33erlrpetertp @LunarWatcher
  • 限制量词根本没有意义。
【解决方案6】:

使用普通正则表达式找到help 后,您需要使用“编辑距离”来查找相似的模式。它是用于拼写检查和单词推荐的指标。例如,如果您从帮助返回所有编辑距离为 1 的单词,您将得到:

helpp
heelp
hellp
hel
belp
...

编辑与help的距离为2:

heeelp
helppp
hhellp

使用 NLTK(Python NLP 包),可以通过以下方式实现:

my_word = 'help'
corpus = {'w1', 'w2'} # Set of all words in your corpus

word_distance = {}

for word in corpus:
    if nltk.edit_distance(my_word, word) <= 2:
        word_distance[word] = nltk.edit_distance(my_word, word)

# Sort dict by value if you choose to return greater edit distances
results = sorted(word_distance, key=word_distance.get, reverse=True)
print(results[:10])

您可以通过正则表达式施加额外的限制以获得更好的结果。例如,nltk.edit_distance 返回的所有内容只有在以h 开头并以p 结尾时才可接受。

【讨论】:

    【解决方案7】:

    我将演示为单词 help 制作正则表达式所需的步骤,但要求不明确,规则不严格,因此通常会出现一些缺点。

    \bh+[a-z&&[^e]]*e+[a-z&&[^le]]*l+[a-z&&[^ p  l  e ]]*p+\b
               ^             ^^              ^  ^  ^
               |             ||              |  |--|-> [#2]
               |             ||              |-> [#1]
               |             ||-> Previous char(s) [#2]
               |             |-> [#1]
               |-> Next immediate character [#1]
    
    • [a-z&amp;&amp;[^lep]] 表示除lep 之外的任何字母

    正则表达式复制/粘贴:

    \bh+[a-z&&[^e]]*e+[a-z&&[^le]]*l+[a-z&&[^lep]]*p+\b
    

    Live demo

    【讨论】:

    • 字符类减法是 Java 特定的。不要尝试使用其他引擎对其进行测试。
    • 使用您提供的链接,并添加测试字符串 This is helppp 它不匹配 - 意味着它在其他单词中不匹配
    • 如果是这种情况,请删除前导和尾随锚点(^$),而不是添加单词边界元字符:\bh+[a-z&amp;&amp;[^e]]*e+[a-z&amp;&amp;[^le]]*l+[a-z&amp;&amp;[^lep]]*p+\b@LunarWatcher
    • 现在它工作得更好了,而且它不会检测到废话(恰好有正确的单词顺序) - 我现在唯一需要做的就是对正则表达式进行逆向工程和为它创建一个方法,这样我就可以用任何单词来做到这一点。但这应该不是很难。最后一个问题:[a-z&amp;&amp;[^lep]] 正则表达式是否必须按特定顺序排列字符,还是只要它存在就可以工作?
    • 要制作正则表达式,您应该只知道在每个单词的字母之后应该有一个+[a-z&amp;&amp;[^*]],其中* 表示每个步骤中的下一个直接字符和前一个字符。不,顺序并不重要。
    猜你喜欢
    • 1970-01-01
    • 2016-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多