我试图在newick格式化的树上翻译节点,而我在更换权限方面遇到了麻烦。说我有HashMap
:
"(1:" : "(30:"
",1:" : ",30:"
"(30:" : "(6:"
",30:" : ",6:"
树:
(30:0.07,(1:0.06,2:0.76))
传统智慧会暗示多个replaceAll
,但这会带来一个问题:
replaceAll("(1:", "(30:") >> (30:0.07,(30:0.06,2:0.76))
replaceAll("(30:", "(6:") >> (6:0.07,(6:0.06,2:0.76))
这里的问题是我们已经替换了之前被替换的节点。正确的树应该如下所示:
(6:0.07,(30:0.06,2:0.76))
现在我已经在Python中完成了这个:
def multiple_replace(taxa, text):
regex = re.compile("|".join(map(re.escape, taxa.keys())))
return regex.sub(lambda mo: taxa[mo.group(0)], text)
但是我的Java实现遇到了问题:
private String convertTree (String treeOld, HashMap<String, String> conv) {
Pattern pattern = Pattern.compile("\\(\\d+:|,\\d+:");
Matcher matcher = pattern.matcher(treeOld);
StringBuilder sbt = new StringBuilder(treeOld);
while (matcher.find()) {
String replace = conv.get(matcher.group());
System.out.println(matcher.group() + "||" +replace + " || " + matcher.start() + ":"+matcher.end());
sbt.delete(matcher.start(), matcher.end());
sbt.insert(matcher.start(), replace);
}
return treeOld;
}
虽然替换似乎有效,但我无法使用不同大小的字符串使索引非常正确(如示例所示)。有没有办法在Java中做到这一点?
答案 0 :(得分:9)
您可以在匹配时使用Matcher#appendReplacement
修改字符串。
请注意,您的正则表达式可以简化为[,(]\d+:
,因为您的备用分支仅在第一个字符中有所不同([,(]
匹配,
或(
)。
这是IDEONE demo:
import java.util.*;
import java.util.regex.*;
import java.lang.*;
import java.io.*;
class Ideone
{
public static void main (String[] args) throws java.lang.Exception
{
String tree = "(30:0.07,(1:0.06,2:0.76))";
HashMap<String, String> h = new HashMap<String, String>();
h.put("(1:" , "(30:");
h.put(",1:" , ",30:");
h.put("(30:" , "(6:");
h.put(",30:" , ",6:");
System.out.println(convertTree(tree, h));
}
private static String convertTree(String treeOld, HashMap<String, String> conv) {
Pattern pattern = Pattern.compile("[,(]\\d+:"); // Init the regex
Matcher m = pattern.matcher(treeOld); // Init the matcher
StringBuffer result = new StringBuffer(); // Declare the string buffer (can be replaced with a string builder)
while (m.find()) { // Iterate through matches
if (conv.containsKey(m.group(0))) { // Check if the key exists
m.appendReplacement(result, conv.get(m.group(0))); // If yes, use the HashMap value
}
else {
m.appendReplacement(result, m.group(0)); // Else, just reinsert the match value
}
}
m.appendTail(result); // Append what remains to the result
return result.toString();
}
}
答案 1 :(得分:7)
想出来,需要使用偏移值:
private String singlePassConvert (String text, HashMap<String, String> conv) {
Pattern pattern = Pattern.compile("\\(\\d+:|,\\d+:");
Matcher matcher = pattern.matcher(text);
int offset = 0;
while (matcher.find()) {
String replace = conv.get(matcher.group());
String head = (String) text.subSequence(0, matcher.start() + offset);
String tail = (String) text.subSequence(matcher.end() + offset, text.length());
text = head + conv.get(matcher.group()) + tail;
if (matcher.group().length() > conv.get(matcher.group()).length()) {
offset --;
} else if (matcher.group().length() < conv.get(matcher.group()).length()) {
offset ++;
}
}
return text;
}
但是,公平警告,因为此实现不使用StringBuilder
,所以在大字符串上可能会很慢。
此外,偏移值仅适用于长度为+/- 1的差异,如果长度差异未知,则应进行修改。