当字符串中存在重复字母时获取匹配项

Osm*_*Osm 4 regex google-sheets re2 google-sheets-formula

我在谷歌表格中有一个输入列表,

输入 所需输出 “仅演示而不是输入”重复的字母
户外活动 匹配
没有匹配
没有匹配
蜜蜂 匹配 e
棋盘 匹配 s
食谱 匹配 好的

如何在不拆分字符串的情况下验证字符串中的所有字母是否唯一?

换句话说,如果字符串中有一个或多个字母出现两次或多次,则返回TRUE

到目前为止我的流程

除了分割字符串并将字符串的长度除以字符串的唯一字母之外,我还尝试了此解决方案,if = 1“匹配”,否则“不匹配”COUNTA

或者使用正则表达式
,我发现了一种方法来匹配字符串中出现的字母 2 次此演示,REGEXEXTRACT 但等待需要的是当字母在字符串中不唯一时得到TRUE

=REGEXEXTRACT(A1,"o{2}?")
Run Code Online (Sandbox Code Playgroud)

返回:

oo
Run Code Online (Sandbox Code Playgroud)

像这样的事情会做

=REGEXMATCH(Input,"(anyletter){2}?")
Run Code Online (Sandbox Code Playgroud)

或者像这样

=REGEXMATCH(lower(A6),"[a-zA-Z]{2}?")
Run Code Online (Sandbox Code Playgroud)

笔记

  • 第三列“C 列”仅用于演示,不用于输入。
  • 匹配不区分大小写
  • 不需要分割字符串以避免繁重的计算“我有很长的列表”
  • 避免使用 lambda 及其辅助函数,明白为什么吗?
  • 返回TRUEorFALSE代替Matchor 就可以了No Match,保持简单。

更多示例

输入 所需输出
专业地 匹配
吸引力 匹配
无法控制地 匹配
声名狼藉地 没有匹配
推荐 匹配
审讯 匹配
攻击性 匹配
双重思想 没有匹配

Jvd*_*vdV 6

您使用单个正则表达式明确要求答案。不幸的是,没有使用 RE2 对以前的捕获组进行反向引用之类的东西。因此,如果您详细说明问题的答案,它将如下所示:

=INDEX(IF(A2:A="","",REGEXMATCH(A2:A,"(?i)(?:a.*a|b.*b|c.*c|d.*d|e.*e|f.*f|g.*g|h.*h|i.*i|j.*j|k.*k|l.*l|m.*m|n.*n|o.*o|p.*p|q.*q|r.*r|s.*s|t.*t|u.*u|v.*v|w.*w|x.*x|y.*y|z.*z)")))
Run Code Online (Sandbox Code Playgroud)

由于您正在寻找不区分大小写的匹配(?i)修饰符,因此将有助于将选项减少到仅 26 个字母表字母。我想上面的内容可以写得更简洁一些,比如:

=INDEX(IF(A2:A="","",REGEXMATCH(A2:A,"(?i)(?:"&TEXTJOIN("|",1,REPLACE(REPT(CHAR(SEQUENCE(26,1,65)),2),2,0,".*"))&")")))
Run Code Online (Sandbox Code Playgroud)

编辑1

使用除上述之外的单个正则表达式执行此操作的唯一其他合理方法(直到我了解@DoubleUnary的 PREG 支持的 matches 子句语法QUERY())是在 GAS 中创建您自己的 UDF(AFAIK)。它将基于 JavaScript,因此支持反向引用。GAS 不是我的强项,但一个简单的例子可以是:

function REGEXMATCH_JS(s) {
  if (s.map) {
    return s.map(REGEXMATCH_JS);
  } else {
    return /([a-z]).*?\1/gi.test(s);
  }
}
Run Code Online (Sandbox Code Playgroud)

该模式的([a-z]).*?\1含义是:

  • ([a-z])- 捕获 az 范围内的单个字符;
  • .*?\1- 通过反向引用查找 0+(惰性)字符,直至第一个捕获的字符的副本。

匹配是全局的并且不区分大小写。您现在可以致电:

=INDEX(IF(A2:A="","",REGEXMATCH_JS(A2:A)))
Run Code Online (Sandbox Code Playgroud)

编辑2

对于那些对速度进行基准测试的人,我自己不会对此进行测试,但也许这会加快速度:

=INDEX(REGEXMATCH(A2:INDEX(A:A,COUNTA(A:A)),"(?i)(?:a.*a|b.*b|c.*c|d.*d|e.*e|f.*f|g.*g|h.*h|i.*i|j.*j|k.*k|l.*l|m.*m|n.*n|o.*o|p.*p|q.*q|r.*r|s.*s|t.*t|u.*u|v.*v|w.*w|x.*x|y.*y|z.*z)"))
Run Code Online (Sandbox Code Playgroud)

或者:

=INDEX(REGEXMATCH(A2:INDEX(A:A,COUNTA(A:A)),"(?i)(?:"&TEXTJOIN("|",1,REPLACE(REPT(CHAR(SEQUENCE(26,1,65)),2),2,0,".*"))&")")) 
Run Code Online (Sandbox Code Playgroud)

或者:

=REGEXMATCH_JS(A2:INDEX(A:A,COUNTA(A:A)))
Run Code Online (Sandbox Code Playgroud)

分别。知道第一行有一个标题。


The*_*ter 5

基准:

在这里创造了一个标杆。

方法:

  • 用于NOW()在单击复选框时创建时间戳。
  • NOW()当最后一行被填充并且复选框被选中时,用于创建另一个时间戳。
  • 这两个时间戳之间的差异给出了完成公式所需的时间。
  • Math.random该样本是根据每个单词 10 个字符创建的随机数据[A-Za-z]

结果:

公式 第1轮 第二轮 平均 % 比最好的慢
样本量 10006
[re2](a.*a|b.*b)JvDv 0:00:19 0:00:19 0:00:19 -15.15%
[re2+递归]MASTERMATCH_RE2 0:00:27 0:00:24 0:00:26 -54.55%
[查找+递归]MASTERMATCH 0:00:17 0:00:16 0:00:17 0.00%
[PREG]双元 0:00:57 0:00:53 0:00:55 -233.33%

结论:

根据浏览器/设备/移动应用程序和非随机样本数据,这有很大差异。但我发现PREG始终比


使用

这看起来 比基于正则表达式的方法要快得多。创建一个命名函数:

姓名:

MASTERMATCH
Run Code Online (Sandbox Code Playgroud)

参数(按此顺序):

word
Run Code Online (Sandbox Code Playgroud)

要检查的单词

start
Run Code Online (Sandbox Code Playgroud)

开始于

功能:

=IF(
  MID(word,start,1)="",
  FALSE,
  IF(
    ISERROR(FIND(MID(word,start,1),word,start+1)),
    MASTERMATCH(word,start+1),
    TRUE
  )
)
Run Code Online (Sandbox Code Playgroud)

用法:

=ARRAYFORMULA(MASTERMATCH(A2:INDEX(A2:A,COUNTA(A2:A)),1))
Run Code Online (Sandbox Code Playgroud)

或者不区分大小写

=ARRAYFORMULA(MASTERMATCH(lower(A2:A),1)) 
Run Code Online (Sandbox Code Playgroud)

解释:

它使用递归遍历每个字符MID,并使用检查该位置之后是否有相同的字符可用FIND。如果是,则返回true并且不再检查。如果没有,则使用递归继续检查直到最后一个字符。


或者使用,创建一个命名函数:

姓名:

MASTERMATCH_RE2
Run Code Online (Sandbox Code Playgroud)

参数(按此顺序):

word
Run Code Online (Sandbox Code Playgroud)

要检查的单词

start
Run Code Online (Sandbox Code Playgroud)

开始于

功能:

IF(
  MID(word,start,1)="",
  FALSE,
  IF(
    REGEXMATCH(word,MID(word, start, 1)&"(?i).*"&MID(word,start,1)),
    TRUE,
    MASTERMATCH_RE2(word,start+1)
  )
)
Run Code Online (Sandbox Code Playgroud)

用法:

=ARRAYFORMULA(MASTERMATCH_RE2(A2:A,1))
Run Code Online (Sandbox Code Playgroud)

或者

=ARRAYFORMULA(MASTERMATCH_RE2(lower(A2:A),1)) 
Run Code Online (Sandbox Code Playgroud)

解释:

它递归遍历每个字符并为该字符创建一个正则表达式。它不是a.*a, b.*b,... ,而是采用第一个字符(使用MID),例如:oinoutdoor并创建一个正则表达式o.*o。如果 regex 对于该正则表达式为正(使用REGEXMATCH),则返回 true 并且不检查其他字母或创建其他正则表达式。


使用,但它很有效。MAP使用和循环遍历每一行和每个字符REDUCEREPLACE找出单词中每个字符的长度差异。如果大于1,则不再检查长度并返回Match

=MAP(
  A2:INDEX(A2:A,COUNTA(A2:A)),
  LAMBDA(_,
    REDUCE(
      "No Match",
      SEQUENCE(LEN(_)),
      LAMBDA(a,c,
        IF(a="Match",a,
          IF(
            LEN(_)-LEN(
              REGEXREPLACE(_,"(?i)"&MID(_,c,1),)
            )>1,
            "Match",a
          )
        )
      )
    )
  )
)
Run Code Online (Sandbox Code Playgroud)

如果您确实遇到 lambda 限制,请删除MAP并拖动填充REDUCE公式。

=REDUCE("No Match",SEQUENCE(LEN(A2)),LAMBDA(a,c,IF(a="Match",a,IF(LEN(A2)-LEN(REGEXREPLACE(A2, "(?i)"&MID(A2,c,1),))>1,"Match",a))))
Run Code Online (Sandbox Code Playgroud)

后者也是条件格式的首选。


dou*_*ary 5

正如 Daniel Cruz 所说,Google Sheets 具有 、 等功能regexmatch()regexextract()regexreplace()使用不支持反向引用的RE2正则表达式。但是,该query()函数使用支持命名捕获组和反向引用的Perl 兼容正则表达式:

\n
=arrayformula( \n  iferror( not( iserror( \n    match( \n      to_text(A3:A), \n      query(lower(unique(A3:A)), "where Col1 matches \'.*?(?<char>.).*?\\k<char>.*\' ", 0), \n      0 \n    ) \n  ) / (A3:A <> "") ) ) \n)\n
Run Code Online (Sandbox Code Playgroud)\n

在我使用来自 TheMaster 语料库的 1000 个hetogrampangram 、带变音字母的单词和 10 个字符的伪随机唯一值的样本大小进行的有限测试中,此 PREG 公式的运行速度大约是 JvdV2 RE2 正则表达式的一半。

\n

Osm 的样本包含 50,000 个高度重复的样本值,该公式的运行速度是 JvdV2 的 8 倍。

\n

PREG 正则表达式比 RE2 正则表达式慢,但优点是您可以更轻松地检查所有字符是否重复。这使您可以使用包含变音字母、数字和其他非英语字母字符的语料库:

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
输入输出
专业地真的
声名狼藉地错误的
算盘真的
\xc3\x89lys\xc3\xa9e真的
na\xc3\xafve\xc3\x8f真的
m\xc3\xa4\xc3\xa4r\xc3\xa4\xc3\xa4v\xc3\xa4真的
121真的
123错误的
\n
\n

<char>.您还可以通过替换为<char>[\\w\xc3\xa9\xc3\xa4\xc3\xa5\xc3\xb6]或之类的内容来轻松指定要检查的特定字符<char>[^-;,.\\s\\d]

\n