【问题标题】:Java Map replaceAll with multiple String matchesJava Map replaceAll 与多个字符串匹配
【发布时间】:2013-12-03 01:49:53
【问题描述】:

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

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

有没有办法安全地替换所有匹配项而不覆盖以前的替换项?

我注意到 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();
    }
}

【问题讨论】:

标签: java regex string replace


【解决方案1】:

我会这样做:

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 臭名昭著的小堆栈空间)被实现为循环并将结果的第一部分(即替换字符串或第一个字符)写入字符串生成器.

如果地图中有一个键是其他键的前缀(即“键”、“键”),您可能需要尝试减小键的长度。

进一步注意,可以设计一个更快的算法,使用 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();
}

【讨论】:

  • 所以我想,我会得到匹配的索引并将等于foundIndex + matchLength 的子字符串设置为字符串的末尾?
  • @Mr.Polywhirl 如果你有 "key" 与 "clef" 关联并且字符串是 "keys of the house",那么结果将是 "clef" + replace("s of the house “, 地图)。 foundIndex 始终为 0(使用 String#startsWith
【解决方案2】:

我的方法如下。可能有更快的解决方案,但如果您喜欢这个想法,可以更进一步。

public String mapReplaceAll5(String str, Map<String, String> replacements) {
    Map<String, String> origToMarker = new HashMap<String, String>();
    Map<String, String> markerToRepl = new HashMap<String, String>();
    char c = 32000;
    for(Entry<String, String> e : replacements.entrySet()) {
        origToMarker.put(e.getKey(), String.valueOf(c));
        markerToRepl.put(String.valueOf(c--), e.getValue());
    }
    for (Map.Entry<String, String> entry : origToMarker.entrySet()) {
        str = str.replaceAll(entry.getKey(), entry.getValue());
    }
    for (Map.Entry<String, String> entry : markerToRepl.entrySet()) {
        str = str.replaceAll(entry.getKey(), entry.getValue());
    }

    return str;
}

【讨论】:

    【解决方案3】:

    您可以在地图中使用StringUtils.replaceEach,但需要将数据复制到一对数组中。

    public String replaceEach(String s, Map<String, String> replacements)
    {
        int size = replacements.size();
        String[] keys = replacements.keySet().toArray(new String[size]);
        String[] values = replacements.values().toArray(new String[size]);
        return StringUtils.replaceEach(s, keys, values);
    }
    

    推荐使用LinkedHashMap,以便定义明确的迭代顺序,但我怀疑这与HashMap 一起工作得很好。

    【讨论】:

      猜你喜欢
      • 2016-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-01
      • 1970-01-01
      • 2012-04-01
      • 2017-11-03
      • 1970-01-01
      相关资源
      最近更新 更多