问题 Java多次替换单次传递


我正在尝试在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中这样做?


13049
2018-05-15 08:17


起源



答案:


您可以使用 Matcher#appendReplacement 在匹配时修改字符串。

请注意,您的正则表达式可以简化为 [,(]\d+: 因为您的替代分支仅在第一个字符中有所不同([,(] 匹配 , 要么 ()。

这是一个 IDEONE演示

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();

    }
}

9
2018-05-15 08:46



比我的尝试更清洁,非常感谢! - Darkstarone


答案:


您可以使用 Matcher#appendReplacement 在匹配时修改字符串。

请注意,您的正则表达式可以简化为 [,(]\d+: 因为您的替代分支仅在第一个字符中有所不同([,(] 匹配 , 要么 ()。

这是一个 IDEONE演示

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();

    }
}

9
2018-05-15 08:46



比我的尝试更清洁,非常感谢! - Darkstarone


想出来,需要使用偏移值:

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的差异,如果长度差异未知,则应进行修改。


7
2018-05-15 08:21



你在发布问题后5分钟就解决了这个问题,得到了+5 upvotes for the question和+4 upvotes for the answer?看起来很腥。 - Krzysztof Krasoń
@krzyk那是为什么?有人可以立即发布问题然后回答;实际上 鼓励。 - Maroun
是的,有可能,但发布后5分钟?有了一堆新代码?对我来说,它看起来不对,而upvotes的数量只会增加那种不好的感觉。 - Krzysztof Krasoń
@krzyk如果你认为一个问题可以帮助其他人和你 有 答案,你可以在同一时间立即发布。没有错。 - Maroun
我觉得它看起来很奇怪@krzyk,但我实际上已经打开了问题并准备发布30分钟而我摆弄。我知道我接近答案而且不想浪费人们的时间,应该再坚持一下吧! - Darkstarone