Java 中查找字串中子字串的第 N 次出現
1. 概述
在使用 Java 時,在較大字串中定位子字串是常見操作。通常,我們可以使用indexOf()
方法來找到子字串的索引。
在本教程中,我們將探索各種方法來解決一個有趣且實用的問題:在較大的字串中尋找子字串的第 n 次出現。
2.問題介紹
標準的indexOf()
方法可以提供我們字串中子字串的索引。例如,我們可以在「 This is a word.
」中找到子字串“a”
的索引( 8
)。 「:
int firstIdx = "This is a word.".indexOf("a");
assertEquals(8, firstIdx);
但是, indexOf(substring)
方法始終傳回子字串第一次出現的索引。因此,有時候,當我們想要知道某個子字串第n次出現的索引時,使用這種方法是很不方便的。讓我們來看一個例子:
final static String INPUT = "a word, a word, a word, a word";
// indexes of "a": "0 8 16 24 "
如上例所示, INPUT
包含四個「 a
」。我們可以透過直接呼叫indexOf(“a”)
來取得第一個“a”索引( 0
)。然而,我們可能需要解決方案來獲取「a」出現的其他索引,例如 8、16 和 24。
接下來我們來看看如何解決這個問題。
為簡單起見,我們將利用單元測試斷言來驗證每種方法是否會傳回預期結果。
3. 找出子字串的第二次出現
在解決「尋找第 n 次出現」問題之前,我們先尋找子字串「a」第二次( n=2
)出現的索引。然後,我們將「尋找第二次出現」解決方案擴展到各種「第n次出現」解決方案。
我們已經了解到, indexOf(“a”)
傳回第一次出現“a”的索引。此外,我們可以將fromIndex int
參數傳遞給indexOf()
方法。 fromIndex
參數指示我們開始搜尋的字元索引。
因此,取得第二次出現的索引的一個簡單想法是呼叫indexOf()
兩次:
- 透過呼叫
indexOf(substring)
來取得第一次出現的索引並將結果保存在變數中,假設是firstIdx
- 透過呼叫
indexOf(substring, firstIdx + substring.length())
來取得第二次出現的索引
接下來,讓我們實作此方法並使用INPUT
字串進行測試:
int firstIdx = INPUT.indexOf("a");
int secondIdx = INPUT.indexOf("a", firstIdx + "a".length());
assertEquals(8, secondIdx);
現在,我們中的一些人可能已經意識到我們可以使用對應的fromIdx
參數來呼叫indexOf()
n 次來取得第 n 次出現的索引。例如,我們可以在上面的測試中加入另一個indexOf()
呼叫來取得第三次出現的索引thirdIdx = INPUT.indexOf(“a”, secondIdx + “a”.length());
。
接下來,我們將「尋找第二次出現」的解決方案擴展為「尋找第n次出現」。
4. 找出子字串的第 N 次出現
通常,我們會使用遞歸或循環來實現重複操作。
4.1.遞迴方法
那麼,我們首先實作遞歸解決方案:
int nthIndexOf(String input, String substring, int nth) {
if (nth == 1) {
return input.indexOf(substring);
} else {
return input.indexOf(substring, nthIndexOf(input, substring, nth - 1) + substring.length());
}
}
實作非常簡單。 nth
變數用作計數器。我們在每個遞歸步驟中減少它的值。
接下來,讓我們用範例資料測試該方法:
int result1 = nthIndexOf(INPUT, "a", 1);
assertEquals(0, result1);
int result2 = nthIndexOf(INPUT, "a", 2);
assertEquals(8, result2);
int result3 = nthIndexOf(INPUT, "a", 3);
assertEquals(16, result3);
int result4 = nthIndexOf(INPUT, "a", 4);
assertEquals(24, result4);
int result5 = nthIndexOf(INPUT, "a", 5);
assertEquals(-1, result5);
如果我們運行一下,測試就會通過。另外,如我們所看到的,當總出現次數小於nth
值時,方法會傳回-1
。
4.2.迭代方法
同樣,我們可以透過迭代方法實現相同的想法:
static int nthIndexOf2(String input, String substring, int nth) {
int index = -1;
while (nth > 0) {
index = input.indexOf(substring, index + substring.length());
if (index == -1) {
return -1;
}
nth--;
}
return index;
}
使用相同輸入的測試也通過:
int result1 = nthIndexOf2(INPUT, "a", 1);
assertEquals(0, result1);
int result2 = nthIndexOf2(INPUT, "a", 2);
assertEquals(8, result2);
int result3 = nthIndexOf2(INPUT, "a", 3);
assertEquals(16, result3);
int result4 = nthIndexOf2(INPUT, "a", 4);
assertEquals(24, result4);
int result5 = nthIndexOf2(INPUT, "a", 5);
assertEquals(-1, result5);
5. 基於正規表示式的解決方案
我們已經了解如何使用indexOf()
方法解決這個問題。或者,我們可以使用Java的Regex API來解決這個問題。
Matcher.find()
允許我們在輸入字串中尋找下一個符合的符合項目。因此,我們可以呼叫Matcher.find()
n 次來獲得第 n 個匹配項。此外,我們可以使用Matcher.start()
來取得每場比賽的開始索引:
int nthOccurrenceIndex(String input, String regexPattern, int nth) {
Matcher matcher = Pattern.compile(regexPattern).matcher(INPUT);
for (int i = 0; i < nth; i++) {
if (!matcher.find()) {
return -1;
}
}
return matcher.start();
}
接下來,讓我們建立一個測試來驗證基於正規表示式的解決方案是否正確完成工作:
int result1 = nthOccurrenceIndex(INPUT, "a", 1);
assertEquals(0, result1);
int result2 = nthOccurrenceIndex(INPUT, "a", 2);
assertEquals(8, result2);
int result3 = nthOccurrenceIndex(INPUT, "a", 3);
assertEquals(16, result3);
int result4 = nthOccurrenceIndex(INPUT, "a", 4);
assertEquals(24, result4);
int result5 = nthOccurrenceIndex(INPUT, "a", 5);
assertEquals(-1, result5);
值得注意的是,該解決方案允許我們匹配與輸入中的模式匹配的動態子字串。然而,另一方面,基於indexOf()
的方法僅適用於固定子字串。
六,結論
在本文中,我們學習了多種方法來定位字串中第 n 次出現的子字串:
- 基於
indexOf()
方法的遞歸解決方案 - 基於
indexOf()
方法的迭代解決方案 - 基於正規表示式的解決方案
與往常一樣,範例的完整原始程式碼可在 GitHub 上取得。