我有以下程序,我想用对应的值替换所有出现的字符串(其中单词作为键在映射中的存在)。

我已经实现了4种方法。它们每个都执行大致相同的功能,但方式不同。前三个的输出不正确,因为下一个替换将覆盖前一个的结果。第四个有效,但这仅是因为我要替换整个字符串中的单个字符。无论如何这都是非常低效的,因为我只检查整个字符串的子字符串。

有没有一种方法可以安全地替换所有出现的事件而不会覆盖以前的替换?

我注意到Apache有一个StringUtils.replaceEach()方法,但是我更喜欢使用地图。

输出:

Apple BApplenApplenApple CApplentApplelope DApplete Apple BApplenApplenApple CApplentApplelope DApplete
Apple BApplenApplenApple CApplentApplelope DApplete Apple BApplenApplenApple CApplentApplelope DApplete
Apple BApplenApplenApple CApplentApplelope DApplete Apple BApplenApplenApple CApplentApplelope DApplete
Apple Banana Cantalope Date Apple Banana Cantalope Date


ReplaceMap.java

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ReplaceMap {
    private static Map<String, String> replacements;

    static {
        replacements = new HashMap<String, String>();
        replacements.put("a", "Apple");
        replacements.put("b", "Banana");
        replacements.put("c", "Cantalope");
        replacements.put("d", "Date");
    }

    public ReplaceMap() {
        String phrase = "a b c d a b c d";

        System.out.println(mapReplaceAll1(phrase, replacements));
        System.out.println(mapReplaceAll2(phrase, replacements));
        System.out.println(mapReplaceAll3(phrase, replacements));
        System.out.println(mapReplaceAll4(phrase, replacements));
    }

    public String mapReplaceAll1(String str, Map<String, String> replacements) {
        for (Map.Entry<String, String> entry : replacements.entrySet()) {
            str = str.replaceAll(entry.getKey(), entry.getValue());
        }

        return str;
    }

    public String mapReplaceAll2(String str, Map<String, String> replacements) {
        for (String key : replacements.keySet()) {
            str = str.replaceAll(Pattern.quote(key),
                    Matcher.quoteReplacement(replacements.get(key)));
        }

        return str;
    }

    public String mapReplaceAll3(String str, Map<String, String> replacements) {
        String regex = new StringBuilder("(")
            .append(join(replacements.keySet(), "|")).append(")").toString();
        Matcher matcher = Pattern.compile(regex).matcher(str);

        while (matcher.find()) {
            str = str.replaceAll(Pattern.quote(matcher.group(1)),
                    Matcher.quoteReplacement(replacements.get(matcher.group(1))));
        }

        return str;
    }

    public String mapReplaceAll4(String str, Map<String, String> replacements) {
        StringBuilder buffer = new StringBuilder();
        String regex = new StringBuilder("(")
            .append(join(replacements.keySet(), "|")).append(")").toString();
        Pattern pattern = Pattern.compile(regex);

        for (int i = 0, j = 1; i < str.length(); i++, j++) {
            String s = str.substring(i, j);
            Matcher matcher = pattern.matcher(s);


            if (matcher.find()) {
                buffer.append(s.replaceAll(Pattern.quote(matcher.group(1)),
                            Matcher.quoteReplacement(replacements.get(matcher.group(1)))));
            } else {
                buffer.append(s);
            }
        }


        return buffer.toString();
    }

    public static String join(Collection<String> s, String delimiter) {
        StringBuilder buffer = new StringBuilder();
        Iterator<String> iter = s.iterator();
        while (iter.hasNext()) {
            buffer.append(iter.next());
            if (iter.hasNext()) {
                buffer.append(delimiter);
            }
        }
        return buffer.toString();
    }

    public static void main(String[] args) {
        new ReplaceMap();
    }
}

最佳答案

因此,我会这样做:

replace(str, map)
    if we have the empty string, the result is the empty string.
    if the string starts with one of the keys from the map:
        the result is the replacement associated with that key + replace(str', map)
             where str' is the substring of str after the key
    otherwise the result is the first character of str + replace(str', map)
             where str' is the substring of str without the first character


请注意,尽管是递归地制定的,但它可以(并且应该由于Java臭名昭著的小堆栈空间)被实现为循环并将结果的第一部分(即替换字符串或第一个字符)写入stringbuilder。

如果地图中的某个键是其他某个键(例如“ key”,“ keys”)的前缀,则您可能需要尝试按递减的长度进行操作。

还要注意,可以设计一种更快的算法,该算法使用Tries代替HasMaps。这也可以解决模棱两可的密钥问题。

这是一个大纲(未经测试):

public static String replace(String it, Map<String, String> map) {
    StringBuilder sb = new StringBuilder();
    List<String> keys = map.keySet();      // TODO: sort by decreasing length!!
    next: while (it.length() > 0) {
        for (String k : keys) {
            if (it.startsWith(k)) {
                // we have a match!
                sb.append(map.get(k));
                it = it.substring(k.length(), it.length());
                continue next;
            }
        }
        // no match, advance one character
        sb.append(it.charAt(0));
        it = it.substring(1, it.length());
    }
    return sb.toString();
}

09-06 15:54