Java 8 Map уменьшить на HashMap как лямбда - PullRequest
0 голосов
/ 07 июня 2018

У меня есть String и я хочу заменить некоторые слова внутри.У меня есть HashMap, где ключ - это заполнитель, который нужно заменить, и значение слова для его замены.Вот мой старый школьный код:

  private String replace(String text, Map<String, String> map) {
    for (Entry<String, String> entry : map.entrySet()) {
      text = text.replaceAll(entry.getKey(), entry.getValue());
    }
    return text;
  }

Есть ли способ написать этот код как лямбда-выражение?

Я попытался entrySet().stream().map(...).reduce(...).apply(...);, но не смог заставить его работать.

Заранее спасибо.

Ответы [ 5 ]

0 голосов
/ 07 июня 2018

Я не думаю, что вы должны пытаться найти более простое или короткое решение, а скорее подумайте о семантике и эффективности вашего подхода.

Вы перебираете карту, которая, вероятно, не имеет указанной итерациипорядок (например, HashMap) и выполнение одной замены за другой, используя результат замены в качестве входных данных для следующего, потенциально пропущенные совпадения из-за ранее примененных замен или замены содержимого в заменах.

Даже если мыПредположим, что вы передаете карту, ключи и значения которой не имеют помех, этот подход очень неэффективен.Далее обратите внимание, что replaceAll будет интерпретировать аргументы как регулярные выражения.

Если мы предполагаем, что регулярные выражения не предназначены, мы можем очистить неоднозначности между ключами, отсортировав их по длине, чтобы сначала пытались использовать более длинные ключи.Тогда решение, выполняющее одну операцию замены, может выглядеть следующим образом:

private static String replace(String text, Map<String, String> map) {
    if(map.isEmpty()) return text;
    String pattern = map.keySet().stream()
        .sorted(Comparator.comparingInt(String::length).reversed())
        .map(Pattern::quote)
        .collect(Collectors.joining("|"));
    Matcher m = Pattern.compile(pattern).matcher(text);
    if(!m.find()) return text;
    StringBuffer sb = new StringBuffer();
    do m.appendReplacement(sb, Matcher.quoteReplacement(map.get(m.group())));
       while(m.find());
    return m.appendTail(sb).toString();
}

начиная с Java 9, вы можете использовать StringBuilder вместо StringBuffer здесь

Если вы протестируете его с помощью

Map<String, String> map = new HashMap<>();
map.put("f", "F");
map.put("foo", "bar");
map.put("b", "B");
System.out.println(replace("foo, bar, baz", map));

, вы получите

bar, Bar, Baz

, демонстрирующий, что замена foo имеет приоритет над заменой f и b в пределах его замены bar не заменяется.

Другое дело, если вы захотите снова заменить совпадения внутри замен.В этом случае вам понадобится либо механизм для управления заказом, либо реализация повторной замены, которая будет возвращаться только тогда, когда больше нет совпадений.Конечно, последний требует осторожности, чтобы обеспечить замены, которые в конечном итоге всегда будут приводить к результату.

Например

private static String replaceRepeatedly(String text, Map<String, String> map) {
    if(map.isEmpty()) return text;
    String pattern = map.keySet().stream()
        .sorted(Comparator.comparingInt(String::length).reversed())
        .map(Pattern::quote)
        .collect(Collectors.joining("|"));
    Matcher m = Pattern.compile(pattern).matcher(text);
    if(!m.find()) return text;
    StringBuffer sb;
    do {
        sb = new StringBuffer();
        do m.appendReplacement(sb, Matcher.quoteReplacement(map.get(m.group())));
           while(m.find());
        m.appendTail(sb);
    } while(m.reset(sb).find());
    return sb.toString();
}
Map<String, String> map = new HashMap<>();
map.put("a", "e1");
map.put("e", "o2");
map.put("o", "x3");
System.out.println(replaceRepeatedly("foo, bar, baz", map));
fx3x3, bx321r, bx321z
0 голосов
/ 07 июня 2018

Для реальных целей ваш непотоковый код просто в порядке.В качестве забавного упражнения вы можете представить каждое отображение в виде Function<String,String> и сократить функции:

Function<String,String> combined = map.entrySet().stream()
        .reduce(
                Function.identity(),
                (f, e) -> x -> f.apply(x).replaceAll(e.getKey(), e.getValue()),
                Function::andThen
        );

return combined.apply(text);
0 голосов
/ 07 июня 2018

Вот гораздо более надежное решение, при условии, что каждое слово отделено пробелом,

String replacement = Stream.of(source.split(" ")).map(token -> map.get(token) != null ? map.get(token) : token)
        .collect(Collectors.joining(" "));
0 голосов
/ 07 июня 2018

Несколько улучшений в коде @RavindraRanwala.

    String replacement = Stream.of(text.split("\\b"))
            .map(token -> map.getOrDefault(token, token))
            .collect(Collectors.joining(""));

1) Использовать Map.getOrDefault из Java 8

2) Разделить на «\ b» для поддержки любого разделителя слов, а не только пробела

0 голосов
/ 07 июня 2018

Вы можете использовать для каждого цикла следующее:

  private String replace(String text, Map<String, String> map) {
      final StringBuilder sb = new StringBuilder(text);
      map.forEach((k,v)->replaceAll(sb, k, v));
      return sb.toString();
  }

Заменить все методы можно определить как:

public static void replaceAll(StringBuilder builder, String from, String to){
    int index = builder.indexOf(from);
    while (index != -1)
    {
        builder.replace(index, index + from.length(), to);
        index += to.length(); // Move to the end of the replacement
        index = builder.indexOf(from, index);
    }
}

Вы также можете использовать StringBuffer, если вы используете несколькорезьб.

...