提取方括號之間的文本
1. 概述
當我們進行文本處理時,從模式中提取特定內容是很常見的。有時,在處理使用方括號封裝有意義信息的數據時,提取方括號內的文本可能對我們來說是一個挑戰。
在本教程中,我們將探索提取方括號之間內容的技術和方法。
2.問題介紹
首先,為了簡單起見,讓我們為問題提出兩個先決條件:
- 沒有嵌套的方括號對- 例如,像
“..[value1 [value2]]..”
模式不會作為我們的輸入。 - 方括號總是配對的——例如,“
.. [value1 …
”是無效輸入。
當討論方括號內的輸入數據時,我們遇到兩種可能的情況:
- 使用一對方括號輸入,如
“..[value]..”
中所示 - 使用多對方括號輸入,如
“..[value1]..[value2]..[value3]…”
所示
展望未來,我們的重點將首先解決單對場景,然後我們將繼續針對涉及多對的情況調整解決方案。在本教程中,我們將用來解決這些挑戰的主要技術是 Java 正則表達式 (regex)。
3. 使用一對方括號輸入
假設我們有一個文本輸入:
String INPUT1 = "some text [THE IMPORTANT MESSAGE] something else";
正如我們所看到的,輸入僅包含一對方括號,我們的目標是獲取中間的文本:
String EXPECTED1 = "THE IMPORTANT MESSAGE";
接下來,讓我們看看如何實現這一目標。
3.1. [.*]
想法
解決此問題的直接方法是提取“ [
”和“ ]
”字符之間的內容。因此,我們可能會想出正則表達式模式“[.*]”
。
但是,我們不能直接在代碼中使用此模式,因為正則表達式使用“ [
”和“ ]
”作為字符類定義。例如, “[0-9]”
類匹配任何數字字符。我們必須對它們進行轉義以匹配文字“ [
”或“ ]
” 。
此外,我們的任務是提取而不是匹配。因此,我們可以將目標匹配放在捕獲組中,以便以後更容易引用和提取:
String result = null;
String rePattern = "\\[(.*)]";
Pattern p = Pattern.compile(rePattern);
Matcher m = p.matcher(INPUT1);
if (m.find()) {
result = m.group(1);
}
assertThat(result).isEqualTo(EXPECTED1);
敏銳的眼睛可能會注意到,我們在上面的代碼中只是逃過了打開“ [
”。這是因為,對於方括號和大括號,如果右方括號或大括號前面沒有相應的開始字符,則正則表達式引擎會按字面解釋它。在我們的示例中,我們轉義了“ \\[
”,因此“ ]
”前面沒有任何開頭“ [
”。因此,“ ]
”將被視為文字“ ]
”字符。
3.2.使用 NOR 字符類
我們通過提取“ [
”和“ ]
”之間的“所有內容”解決了這個問題。在這裡, “所有內容”都由不是']'
的字符組成.
正則表達式支持 NOR 類。例如, “[^0-9]”
匹配任何非數字字符。因此,我們可以通過使用正則表達式 NOR 類來優雅地解決這個問題,從而產生模式“ \\[([^]]*)
”:
String result = null;
String rePattern = "\\[([^]]*)";
Pattern p = Pattern.compile(rePattern);
Matcher m = p.matcher(INPUT1);
if (m.find()) {
result = m.group(1);
}
assertThat(result).isEqualTo(EXPECTED1);
3.3.使用split()
方法
Java 提供了強大的String.split()
方法來將輸入字符串分成幾部分。 split()
支持正則表達式模式作為分隔符。接下來,讓我們看看我們的問題是否可以通過split()
方法來解決。
考慮“prefix[value]suffix”.
如果我們指定 ' [
' 或 ' ]
' 作為分隔符, split()
將生成一個數組: {“prefix”, “value”, “suffix”}
。下一步相對簡單。我們可以簡單地從數組中取出中間元素作為結果:
String[] strArray = INPUT1.split("[\\[\\]]");
String result = strArray.length == 3 ? strArray[1] : null;
assertThat(result).isEqualTo(EXPECTED1);
在上面的代碼中,我們確保在從數組中取出第二個元素之前,分割結果應始終具有三個元素。
當我們運行測試時,測試就通過了。但是,如果輸入以 ' ]
' 結尾,則此解決方案可能會失敗:
String[] strArray = "[THE IMPORTANT MESSAGE]".split("[\\[\\]]");
assertThat(strArray).hasSize(2)
.containsExactly("", "THE IMPORTANT MESSAGE");
正如上面的測試所示,這次我們的輸入沒有“ prefix
”和“ suffix
”。默認情況下, split()
會丟棄尾隨的空字符串。為了解決這個問題,我們可以向split()
傳遞一個負數limit
,
告訴split()
保留空字符串元素:
strArray = "[THE IMPORTANT MESSAGE]".split("[\\[\\]]", -1);
assertThat(strArray).hasSize(3)
.containsExactly("", "THE IMPORTANT MESSAGE", "");
因此,我們可以改變我們的解決方案來覆蓋極端情況:
String[] strArray = INPUT1.split("[\\[\\]]", -1);
String result = strArray.length == 3 ? strArray[1] : null;
...
4. 多個方括號對的輸入
在解決了單個“[..]”
對案例之後,擴展解決方案以處理多個“[..]”
案例對我們來說不會是一個挑戰。讓我們看一個新的輸入示例:
final String INPUT2 = "[La La Land], [The last Emperor], and [Life of Pi] are all great movies.";
接下來,讓我們從中提取三個電影標題:
final List<String> EXPECTED2 = Lists.newArrayList("La La Land", "The last Emperor", "Life of Pi");
4.1. [(.*)]
想法 – 非貪婪版本
模式“\\[(.*)]”
有效地促進了從單個“[..]”對中提取所需內容。但這對於具有多個“[..]”
對的輸入不起作用。這是因為正則表達式默認進行貪婪匹配。換句話說,如果我們將INPUT2
與“\\[(.*)]”
匹配,捕獲組將保留第一個“ [
”和最後一個“ ]
”之間的文本:“ La La Land], [The last Emperor], [Life of Pi
》。
但是,我們可以添加一個 ' ?
' 在 '*' 之後確保正則表達式進行非貪婪匹配。此外,由於我們將提取多個目標值,因此我們將if (m.find())
更改為while
循環:
List<String> result = new ArrayList<>();
String rePattern = "\\[(.*?)]";
Pattern p = Pattern.compile(rePattern);
Matcher m = p.matcher(INPUT2);
while (m.find()) {
result.add(m.group(1));
}
assertThat(result).isEqualTo(EXPECTED2);
4.2.使用字符類
NOR 字符類解決方案也適用於具有多個“[..]”
對的輸入。我們只需要將if
語句改為while
循環即可:
List<String> result = new ArrayList<>();
String rePattern = "\\[([^]]*)";
Pattern p = Pattern.compile(rePattern);
Matcher m = p.matcher(INPUT2);
while (m.find()) {
result.add(m.group(1));
}
assertThat(result).isEqualTo(EXPECTED2);
4.3.使用split()
方法
對於具有多個“ [..]
”的輸入,如果我們通過相同的正則表達式split()
,結果數組應該具有三個以上的元素。所以,我們不能簡單地取中間(index=1)
的一個:
Input: "---[value1]---[value2]---[value3]---"
Array: "---", "value1", "---", "value2", "---", "value3", "---"
Index: [0] [1] [2] [3] [4] [5] [6]
但是,如果我們查看索引,我們會發現所有具有奇數索引的元素都是我們的目標值。因此,我們可以編寫一個循環來從split()
的結果中獲取所需的元素:
List<String> result = new ArrayList<>();
String[] strArray = INPUT2.split("[\\[\\]]" -1);
for (int i = 1; i < strArray.length; i += 2) {
result.add(strArray[i]);
}
assertThat(result).isEqualTo(EXPECTED2);
5. 結論
在本文中,我們學習瞭如何在 Java 中提取方括號之間的文本。我們學習了不同的正則表達式相關方法來應對挑戰,有效地解決了兩個問題場景。
與往常一樣,示例的完整源代碼可在 GitHub 上獲取。