【问题标题】:Java search a string for a substring UNLESS preceded by specific characterJava 在字符串中搜索子字符串,除非前面有特定字符
【发布时间】:2017-02-25 21:20:03
【问题描述】:

我正在尝试编写一个 Java 程序,该程序在用户输入的字符串中搜索特定的子字符串 (xyz),并保持运行计数,除非该子字符串前面有句点。在课堂上,我们只使用了 charAt 和 length,所以如果可能的话,我需要坚持下去。此外,我们根本没有使用过正则表达式,所以这也是不可能的。

我已经设法让程序按需要运行,但有一个明显的例外:如果输入的字符串 以句点开头,则它无法计算任何连续匹配。这是我到目前为止所得到的:

System.out.println("Give me a String:");
String s1 = kb.nextLine();

int index = 0;
int count = 0;

while(index <= s1.length() - 1 && s1.charAt(index) != '.')
{
        if(s1.charAt(index) == 'x' && s1.charAt(index + 2) == 'z')
        {
            count++;
        }
        index++;
}
System.out.println(count);

【问题讨论】:

  • 提示:你需要两个循环。第一个循环遍历用户输入,char-by-char。第二个嵌套循环迭代 xyz 并匹配用户输入的当前字符。
  • 我可能已经知道这个问题的答案,但是是否可以使用嵌套的 while 循环来完成此任务,或者是否需要使用 for 循环?如果两者都是可能的,那么使用 for 循环是否会更清洁?不幸的是,我发现很难理解嵌套 for 循环如何在没有反复试验和错误的情况下工作,但如果这是这里的最佳解决方案,那么这就是我需要尝试做的事情。泰!
  • 提示 - 你有一个逻辑问题。考虑输入:xyzxazxfz 或输入:x
  • 似乎还要求忽略任何立即以“。”开头的“xyz”。因为您说“如果输入的字符串以句点开头,则它无法计算任何连续匹配”,因此您还需要考虑输入 xyz.axyz - 这应该输出 2 但上面代码的输出是 1
  • 假设我理解正确,要求是计算“xyz”子字符串的数量,忽略所有“.xyz”子字符串。如果是这种情况,那么您可以使用单个循环来完成。

标签: java string loops substring


【解决方案1】:
You can simply check the input string whether it starts with period. If so then  you can use the following piece of code to handle the validation.

if(s1.charAt(0)!='.')
{
while(index <= s1.length() - 1 && s1.charAt(index) != '.')
{
        if(s1.charAt(index) == 'x' && s1.charAt(index + 2) == 'z')
        {
            count++;
        }
        index++;
}
}
else
{
    index=1;
    while(index <= s1.length() - 1 && s1.charAt(index) != '.')
    {
            if(s1.charAt(index) == 'x' && s1.charAt(index + 2) == 'z')
            {
                count++;
            }
            index++;
    }
}
System.out.println(count);
}

【讨论】:

  • 编辑后的代码只包含String的charAt和length属性
  • 谢谢你!我有一种感觉,以句点开头的字符串的问题可以通过 if/else 语句来解决,而且答案出奇的简单(只需在 else 部分中将索引从 1 开始)。也就是说,现在很明显我的原始代码没有像我想象的那样工作。一旦我弄清楚了,将发布完整的代码。非常感谢!
【解决方案2】:

由于这似乎是一个家庭作业类型的问题,我将尝试先引导您朝着正确的方向前进,然后再提供解决方案。 我强烈建议您在查看我的解决方案(一旦我发布后)之前,先尽自己最大的能力自行解决问题,并在继续之前阅读this page

首先,考虑您可以接收的输入类型。由于您没有指定任何限制,因此您可以获得以下内容:

  • “”(空字符串)
  • “\n”(空格)
  • “x”(单个字符)
  • “xx”(两个字符串)
  • “abc”(长度正确的字符串,但不包含您的子字符串)
  • “.xyz”(要忽略的子字符串)

我可以继续说下去,但我相信你可以想出所有你可能收到的奇怪东西的各种组合。这些只是帮助您入门的几个示例(以及我已经在 cmets 中发布的示例)

接下来,想想你需要你的算法做什么。正如我在 cmets 中所说,听起来您想计算子字符串“xyz”的出现次数,而忽略子字符串“.xyz”的出现次数。现在考虑您将如何查找这些子字符串 - 您将在字符串中一次从左到右推进一个字符,以寻找与这两种可能性之一匹配的子字符串。当您找到其中之一时,您要么忽略它,要么数数它。

希望这会有所帮助,正如我所说,稍后我会在您有时间处理代码后发布解决方案。如果您确实解决了它,请继续发布您的解决方案(也许编辑您的问题以添加新代码或添加答案)最后我再次强烈建议您阅读this page如果您还没有。

编辑#1:

我想添加更多信息,那就是:您已经非常清楚此时需要做什么才能计算您的“xyz”子字符串 - 尽管输入逻辑存在小缺陷像“xaz”,很容易修复。您需要关注的是如何忽略子字符串“.xyz”,因此请考虑如何实现忽略逻辑,忽略它意味着什么?一旦你回答它应该开始为你聚集在一起。

编辑 #2:

您将在下面找到我对问题的解决方案。再次重要的是要了解解决方案的工作原理,而不仅仅是复制和粘贴它。如果您只是在不理解我的代码的情况下复制它,那么您就是在欺骗您自己想要获得的教育。目前我没有时间详细描述此代码为何以及如何工作,但我确实计划稍后再次编辑以添加这些详细信息。

import java.util.Scanner;

public class Main {
    private static Scanner scan = new Scanner(System.in);

    public static void main(String[] args) {
        System.out.println("Give me a String:");
        String s1 = scan.nextLine();

        System.out.println(countSubstrings(s1));

    }

    public static int countSubstrings(String s1){
        int index = 0;
        int count = 0;

        while (index < s1.length()-2) {
            if(s1.charAt(index) == '.' && s1.charAt(index+1) != '.'){
                index++;
            }
            else if (index+2 < s1.length() && s1.charAt(index) == 'x' && s1.charAt(index + 1) == 'y'
                    && s1.charAt(index + 2) == 'z') {
                count++;
                index+=2;
            }
            index++;
        }
        return count;
    }

}

编辑#3:

以下是上述代码为何如此运作的具体细节。首先,我们考虑这样一个事实,即我们正在数组中以特定顺序查找 3 个项目(一个三元组),如果我们在三元组的第一个项目之前看到第四个项目(一个句点),那么我们需要忽略三重奏。

根据我之前的编辑,我们需要定义忽略的含义。在这种情况下,我们的意思是根本不计算它,然后继续搜索要计算的有效子字符串。最简单的方法是在不增加count 的情况下推进index

所以,问自己以下问题:

  • 我的循环应该何时停止?因为我们正在寻找三元组,所以我们知道如果输入字符串的长度小于 3 或者当我们尚未检查的字符串中剩下的字符少于 3 个时,我们可以停止。例如,如果当我们到达索引 3 时输入是“xyzab”,我们知道不可能形成一个三元组,其中“a”是三元组中的第一个字符,因此我们的计数已经完成。

  • 是否曾经我不想在一段时间后跳过接下来的 3 个字符?毕竟目标是寻找三元组,所以我不想跳过 3 个字符而不仅仅是 1 个字符吗?是的,有时您确实 想要跳过 3 个字符,那时您有类似“.axyz”的内容,因为有效的三元组可以在第二个字符超过句点时开始。所以实际上你只想跳过 1 个字符。

这一点,以及 index 在循环结束时总是加 1 的事实(稍后会详细介绍),这就是为什么 while 内的第一个条件仅将 index 提前 1:

if(s1.charAt(index) == '.' && s1.charAt(index+1) != '.'){
    index++;
}
  • 是否曾经我会看到一个句号并且不想忽略(跳过)下一个字符?是的,当下一个字符是另一个句点时,因为它可能表明需要跳过另一个三元组。考虑输入“..xyz”,如果您遇到第一个句点并跳过第二个句点,这将导致错误答案,因为您的算法可以将接下来的三个字符视为有效的三元组,但实际上由于第二个句点它是无效的.

这就是上述条件后半部分存在的原因:

`&& s1.charAt(index+1) != '.'`
  • 现在问问自己如何识别有效的三元组。我确定现在您可以了解如何执行此操作 - 检查当前字符、下一个字符以及之后的字符是否有您想要的值。这个逻辑是while内第二个if条件的后半部分:

    s1.charAt(index) == 'x' && s1.charAt(index + 1) == 'y' && s1.charAt(index + 2) == 'z'

每当您在循环内使用索引 +1 或索引 +2 等计算时,该循环会增加索引直到达到边界,您必须考虑计算超出边界的可能性,因为您不能依赖在循环上为您检查这一点,因为循环在循环结束或开始之前不会执行该检查(取决于它是哪种循环)

  • 考虑到上述情况,您必须问自己:当我使用这些 index+1、index+2 等类型的计算时,如何防止超出边界的情况?答案是在您的条件中添加另一部分:

index+2 &lt; s1.length()

您可能想知道 - 为什么不添加两个检查,因为我们使用的是 index+1 和 index+2?在这种情况下,我们只需要检查一下我们使用的最大索引是否会超出边界。如果 index +2 超出范围,我们不关心 index+1 是否存在,因为这无关紧要,我们不可能有匹配的子字符串。

  • 接下来,在 while 内的第二个 if 内,您会看到将索引增加 2 的代码:index+=2; 这是为了提高效率,因为一旦我们确定了三元组,我们就知道没有办法用已经属于另一个三元组的字符组成另一个三元组。因此,我们想跳过它们,就像第一个要点一样,我们利用循环递增索引,所以我们只需要递增 2,然后让循环添加额外的 1。

最后我们到达循环内逻辑的结尾。这部分您已经很熟悉了,这就是index++;,它只是增加了我们当前正在检查的字符串中的位置。请注意,这与第一个要点协同工作。以“.axyz”的第一个要点为例。索引 0 中有一个句点,索引 1 中的字符不是另一个句点,因此第一个项目符号点的逻辑会将索引增加 1,使其为 1。在循环结束时,索引再次增加,使其为 2,从而跳过在此期间 - 在下一个循环开始时索引为 2,在循环开始时它从不是 1。

嗯,我希望这有助于解释它是如何工作的,并说明如何思考这些问题。基本原则是可视化当前元素的位置以及如何使用它来实现目标。同时考虑程序的不同元素具有什么样的属性以及如何利用它们 - 例如,一旦识别出三元组,就可以安全地跳过这些字符,因为它们具有只能使用一次。与任何程序一样,您总是希望尝试创建尽可能多的测试输入,以测试可能发生的所有奇怪的边界情况,以确保代码的正确性。我知道您可能不熟悉JUnit,但它是一个非常有用的工具,您可以在空闲时间尝试研究使用它的基础知识,而且如果您使用Eclipse IDE,它有您可以使用的集成 JUnit 功能。

【讨论】:

  • 感谢您的指导,D.B.为了充分披露,是的,这是一个硬件问题。如果我给人的印象是我试图混淆这一点,我很抱歉。在某一时刻,我的代码正在处理提到的一个异常,但在这篇文章之前,我必须在此过程中改变了一些东西。明天我会更努力地解决这个问题,并希望能解决它。如果没有,请随时发布解决方案,因为它将于明天晚上到期。再次感谢。
  • @SunDevil329 我在答案中添加了一些建议,我认为这些建议在您今天研究算法时可能有用。
  • 事实证明,我的工作时间比我想象的要多,所以我只阅读了您的第一次编辑。虽然我对如何解决逻辑和边界问题有一个好主意,但处理忽略与前一个时期匹配的算法,我预计这将是最具挑战性的方面。我明天肯定会首先解决这个问题,然后将这个问题标记为已解决。我非常感谢深入的解释,因为我真的很想了解它是如何工作的。正如你所说,从长远来看,复制和粘贴只会伤害我。
  • 如果有帮助,请写下几个示例并在纸上逐步完成。您还可以在代码的关键区域添加日志记录,这些区域会让您感到困惑或可能有用(一些对System.out.println(...) 的调用将在此处用于您的目的)。有许多不同的方法可以实现你的目标,所以如果你的代码看起来有点不同,从功能的角度来看它可能完全没问题。彻底测试可以告诉您是否有问题。
猜你喜欢
  • 2018-09-29
  • 1970-01-01
  • 1970-01-01
  • 2019-10-06
  • 2019-09-03
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
相关资源
最近更新 更多