如果对于字符串中的每个“*”(星号),如果星号之前和之后都有字符,则返回 true

Returns true if for every '*' (star) in the string, if there are chars both immediately before and after the star, they are the same

提问人:Evgeniy 提问时间:5/8/2022 最后编辑:Evgeniy 更新时间:5/9/2022 访问量:178

问:

此任务是在 Codingbat 上制定的。(https://codingbat.com/prob/p194491):

如果对于字符串中的每个“*”(星号),如果星号之前和之后都有字符,则返回 true,它们相同。

sameStarChar("xy*yzz") → true
sameStarChar("xy*zzz") → false
sameStarChar("*xa*az") → true

我怎样才能修复我的解决方案,以便使用正则表达式解决此任务?

我的尝试:

public boolean sameStarChar(String str) {
  return str.matches(".*([*])*.*");
}

我的解决方案对超过一半的测试是正确的,但并非对所有测试都是正确的。

Java 正则表达式

评论

0赞 Turing85 5/8/2022
这个问题尚不清楚。字符串呢?该方法应该返回还是?"a***b"truefalse
3赞 Sweeper 5/8/2022
我不明白为什么是真的?第一颗星后面有一个“x”,但前面没有“x”,所以它不应该返回 false 吗?*xa*az
3赞 Elliott Frisch 5/8/2022
有些人在遇到问题时会想:“我知道,我会使用正则表达式。现在他们有两个问题。——杰米·扎温斯基
1赞 dangling else 5/8/2022
@Sweeper - 措辞说“如果星辰前后都有字符”。没有提到“如果之前和之后没有字符”,但我想从第三个例子中我们可以猜到这相当于一个匹配。
0赞 rzwitserloot 5/8/2022
为什么关闭?为此使用正则表达式是一件愚蠢的事情,但这不是关闭的理由。细节很好 - 问题很清楚,例如 是真的,因为:“如果之前和之后都有字符......”。没有,所以,* 是无关紧要的,也不是字符串无效的理由。同样地?这是有效的:只有 1 颗星的“紧接在前面和之后的字符”子句成立,中间星号之前和之后的字符都有效。是相同的,因此是有效的。*xa***

答:

3赞 rzwitserloot 5/8/2022 #1

这是可以做到的,但它非常复杂。你的正则表达式只是扫描“任何东西,然后是任意数量的星星,然后是任何东西——它们与所有东西都匹配。您的代码实际上返回了所有可以想象的字符串。true

你的方法有问题。尝试积极匹配是相当复杂的。问题是在问否定的:“这个结构是无效的;任何没有无效构造的字符串都是有效的“,这就是它归结为,无效构造是:”A*B“,其中 A 和 B 不相同。毕竟,是有效的(示例 3)。大概也是有效的(第一颗和最后一颗星星不是问题,因为它们两边都没有字符,中间的星星很好,因为每边的字符都是相同的)。*a***

因此,您想要的是编写一个查找无效构造的正则表达式,并返回逆向结构。

要找到无效的结构,你需要一个叫做反向引用的东西:你想搜索一个东西,然后引用它。

".\*."- 我们从这里开始:一个角色、一个明星和一个角色。但是现在我们需要第二个字符(第二个实际上是:第一个字符以外的东西)。毕竟,也会匹配,这是一个有效的结构,所以我们不希望它匹配。.".\*.""A*A"

输入 backrefs。 在正则表达式中,会创建一个“组”——你以后可以参考这个东西。 是一个 backref - 它是“与第一组括号匹配的任何内容”。()\1

但我们需要更多——我们需要否定:如果不是这样,就匹配。在 chargroups 中,有 - 表示:“任何不是'f'或'o'的字符”。但 backref 不是字符组。^[^fo]

根据这个支持我的 SO 问题,唯一的方法是消极的展望。Lookahead 是一种你实际上并不匹配字符的东西,你只是检查它们是否匹配,如果匹配,匹配失败。它。。复杂。在网络上搜索解释“积极展望”和“消极展望”的教程。

因此:

Pattern p = Pattern.compile("(.)\\*(?!\\1|$)");

return !p.matcher(str).find();

这里正在发生各种各样的事情:

  • (?!X)是负面的展望。
  • \1|$表示:“组 1”或“字符串末尾”。假设我们的输入包含 ,那么该星号之后的下一件事必须是 X 或字符串的末尾 - 如果是其他任何东西,我们应该返回 false。X*
  • 我们不想匹配整个字符串。我们只想问:“无效构造”在这个字符串的任何地方吗?- 因此,不是 .find()matches()

需要明确的是,为此使用正则表达式可能是一个坏主意。当然,代码会非常短,但它并不完全可读,是吗。

如果没有正则表达式,则更容易遵循:

for (int i = 1; i < str.length() -1; i++) {
  if (str.charAt(i) != '*') continue;
  if (str.charAt(i - 1) != str.charAt(i + 1)) return false;
}
return true;

我强烈更喜欢上述内容,而不是一个不容易显示它实际完成什么的正则表达式,而且这个正则表达式肯定不能仅仅通过查看它来理解它的作用。

2赞 Wiktor Stribiżew 5/9/2022 #2

你可以使用

public boolean sameStarChar(String str) {
     return str.matches("^(?!.*(.)\\*(?!\\1|$)).*");
}

细节

  • ^- 字符串的开头
  • (?!.*(.)\*(?!\1|$))- 如果存在,则无法匹配的负面展望
    • .*- 除换行符以外的任何零个或多个字符,尽可能多
    • (.)- 组 1 ():除换行符以外的任何一个字符\1
    • \*-星号
    • (?!\1|$)- 不紧跟与组 2 或字符串末尾相同的值
  • .*- 字符串的其余部分被消耗掉(因为需要完整的字符串匹配)。.matches

enter image description here

评论

0赞 Anonymous 5/9/2022
这也适用于我的测试示例。令人 印象 深刻。(^ - 字符串的开头是多余的,因为正如您所说,需要整个字符串?matches()
1赞 Wiktor Stribiżew 5/9/2022
@OleV.V.在许多情况下,默认情况下匹配锚定在字符串的开头,我仍然使用 ,只是因为以后在在线测试器中调试会更容易。当然,您可以在此处省略它。^