Java 中的多个同时子字符串替换
Multiple simultaneous substring replacements in Java
(我来自 python 世界,所以如果我使用的一些术语符合规范,我深表歉意。)
我有一个 String
和 List
个 start/end 个索引要替换。无需太多细节,请考虑以下基本模型:
String text = "my email is foo@bar.com and my number is (213)-XXX-XXXX"
List<Token> findings = SomeModule.someFnc(text);
并且 Token
具有
的定义
class Token {
int start, end;
String type;
}
此 List
表示我要编辑的敏感数据的开始和结束位置。
实际上,我迭代得到的 API returns 数据:
[{ "start" : 12, "end" : 22, "type" : "EMAIL_ADDRESS" }, { "start" : 41, "end" : 54, "type" : "PHONE_NUMBER" }]
使用这些数据,我的最终目标是编辑 text
中由这些 Token
对象指定的标记以得到:
"my email is [EMAIL_ADDRESS] and my number is [PHONE_NUMBER]"
这个问题之所以重要,是因为替换子串的长度并不总是与它们要替换的子串的长度相同。
我目前的行动计划是从 text
构建一个 StringBuilder
,按照起始索引的相反顺序对这些 ID 进行排序,然后从缓冲区的右端开始替换。
但是有些东西告诉我应该有更好的方法...有吗?
提取开始和结束之间的子串,并以此分割。然后你得到一个包含 2 个元素的数组,在它们之间插入你想要的内容。接下来,您必须移动您的下一个字符串,以通过(您替换的前一个字符串的长度)和(您放置在其位置的字符串)之间的差异来替换 ids。
代码(如果Token中的'end'是独占的):
public class Main {
public static void main(String... args) {
String text = "I want to replace AAA and B and scary wombat";
Token[] tokens = {new Token(18, 21, "TEST"), new Token(26, 27, "TEST"), new Token(32, 44, "TEST")};
int delta = 0;
for (Token token : tokens) {
String splitter = text.substring(token.start + delta, token.end + delta);
System.out.println("Splitter: " + splitter);
delta += token.replacement.length() - splitter.length();
String[] beforeAndAfter = text.split(Pattern.quote(splitter));
text = beforeAndAfter[0] + token.replacement +
(beforeAndAfter.length == 2 ? beforeAndAfter[1] : ""); // in case where there are no more chars after splitter in text
}
System.out.println(text);
}
static class Token {
public final int start, end;
public final String replacement;
public Token(int start, int end, String replacement) {
this.start = start;
this.end = end;
this.replacement = replacement;
}
}
}
这种方法有效:
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
String text = "my email is foo@bar.com and my number is (213)-XXX-XXXX";
List<Token> findings = new ArrayList<>();
findings.add(new Token(12, 22, "EMAIL_ADDRESS"));
findings.add(new Token(41, 54, "PHONE_NUMBER"));
System.out.println(replace(text, findings));
}
public static String replace(String text, List<Token> findings) {
int position = 0;
StringBuilder result = new StringBuilder();
for (Token finding : findings) {
result.append(text.substring(position, finding.start));
result.append('[').append(finding.type).append(']');
position = finding.end + 1;
}
return result.append(text.substring(position)).toString();
}
}
class Token {
int start, end;
String type;
Token(int start, int end, String type) {
this.start = start;
this.end = end;
this.type = type;
}
}
输出:
my email is [EMAIL_ADDRESS] and my number is [PHONE_NUMBER]
确保所有标记按 start
索引升序排序:
List<Token> tokens = new ArrayList<>();
tokens.sort(Comparator.comparing(Token::getStart));
现在您可以替换从输入文本末尾开始的所有字符串:
public String replace(String text, List<Token> tokens) {
StringBuilder sb = new StringBuilder(text);
for (int i = tokens.size() - 1; i >= 0; i--) {
Token token = tokens.get(i);
sb.replace(token.start, token.end + 1, "[" + token.type + "]");
}
return sb.toString();
}
(我来自 python 世界,所以如果我使用的一些术语符合规范,我深表歉意。)
我有一个 String
和 List
个 start/end 个索引要替换。无需太多细节,请考虑以下基本模型:
String text = "my email is foo@bar.com and my number is (213)-XXX-XXXX"
List<Token> findings = SomeModule.someFnc(text);
并且 Token
具有
class Token {
int start, end;
String type;
}
此 List
表示我要编辑的敏感数据的开始和结束位置。
实际上,我迭代得到的 API returns 数据:
[{ "start" : 12, "end" : 22, "type" : "EMAIL_ADDRESS" }, { "start" : 41, "end" : 54, "type" : "PHONE_NUMBER" }]
使用这些数据,我的最终目标是编辑 text
中由这些 Token
对象指定的标记以得到:
"my email is [EMAIL_ADDRESS] and my number is [PHONE_NUMBER]"
这个问题之所以重要,是因为替换子串的长度并不总是与它们要替换的子串的长度相同。
我目前的行动计划是从 text
构建一个 StringBuilder
,按照起始索引的相反顺序对这些 ID 进行排序,然后从缓冲区的右端开始替换。
但是有些东西告诉我应该有更好的方法...有吗?
提取开始和结束之间的子串,并以此分割。然后你得到一个包含 2 个元素的数组,在它们之间插入你想要的内容。接下来,您必须移动您的下一个字符串,以通过(您替换的前一个字符串的长度)和(您放置在其位置的字符串)之间的差异来替换 ids。
代码(如果Token中的'end'是独占的):
public class Main {
public static void main(String... args) {
String text = "I want to replace AAA and B and scary wombat";
Token[] tokens = {new Token(18, 21, "TEST"), new Token(26, 27, "TEST"), new Token(32, 44, "TEST")};
int delta = 0;
for (Token token : tokens) {
String splitter = text.substring(token.start + delta, token.end + delta);
System.out.println("Splitter: " + splitter);
delta += token.replacement.length() - splitter.length();
String[] beforeAndAfter = text.split(Pattern.quote(splitter));
text = beforeAndAfter[0] + token.replacement +
(beforeAndAfter.length == 2 ? beforeAndAfter[1] : ""); // in case where there are no more chars after splitter in text
}
System.out.println(text);
}
static class Token {
public final int start, end;
public final String replacement;
public Token(int start, int end, String replacement) {
this.start = start;
this.end = end;
this.replacement = replacement;
}
}
}
这种方法有效:
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
String text = "my email is foo@bar.com and my number is (213)-XXX-XXXX";
List<Token> findings = new ArrayList<>();
findings.add(new Token(12, 22, "EMAIL_ADDRESS"));
findings.add(new Token(41, 54, "PHONE_NUMBER"));
System.out.println(replace(text, findings));
}
public static String replace(String text, List<Token> findings) {
int position = 0;
StringBuilder result = new StringBuilder();
for (Token finding : findings) {
result.append(text.substring(position, finding.start));
result.append('[').append(finding.type).append(']');
position = finding.end + 1;
}
return result.append(text.substring(position)).toString();
}
}
class Token {
int start, end;
String type;
Token(int start, int end, String type) {
this.start = start;
this.end = end;
this.type = type;
}
}
输出:
my email is [EMAIL_ADDRESS] and my number is [PHONE_NUMBER]
确保所有标记按 start
索引升序排序:
List<Token> tokens = new ArrayList<>();
tokens.sort(Comparator.comparing(Token::getStart));
现在您可以替换从输入文本末尾开始的所有字符串:
public String replace(String text, List<Token> tokens) {
StringBuilder sb = new StringBuilder(text);
for (int i = tokens.size() - 1; i >= 0; i--) {
Token token = tokens.get(i);
sb.replace(token.start, token.end + 1, "[" + token.type + "]");
}
return sb.toString();
}