Java8: HashMap для HashMap с использованием Stream / Map-Reduce / Collector



Я знаю, как" преобразовать " простой Java List С Y ->Z, например:



List<String> x;
List<Integer> y = x.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());


теперь я хотел бы сделать в основном то же самое с картой, т. е.:



INPUT:
{
"key1" -> "41", // "41" and "42"
"key2" -> "42 // are Strings
}

OUTPUT:
{
"key1" -> 41, // 41 and 42
"key2" -> 42 // are Integers
}


решение не должно ограничиваться String ->Integer. Так же, как и в List выше примере, я хотел бы вызвать любой метод (или конструктор).

1458   9  

9 ответов:

Map<String, String> x;
Map<String, Integer> y =
    x.entrySet().stream()
        .collect(Collectors.toMap(
            e -> e.getKey(),
            e -> Integer.parseInt(e.getValue())
        ));

это не совсем так хорошо, как код списка. Вы не можете построить новый Map.EntryС map() вызов так что работа смешивается в collect() звонок.

вот некоторые вариации на ответ Сотириоса Делиманолиса, что было довольно хорошо для начала (+1). Рассмотрим следующее:

static <X, Y, Z> Map<X, Z> transform(Map<? extends X, ? extends Y> input,
                                     Function<Y, Z> function) {
    return input.keySet().stream()
        .collect(Collectors.toMap(Function.identity(),
                                  key -> function.apply(input.get(key))));
}

несколько моментов здесь. Во-первых, это использование подстановочных знаков в обобщениях; это делает функцию несколько более гибкой. Подстановочный знак будет необходим, если, например, вы хотите, чтобы выходная карта имела ключ, который является суперклассом ключа входной карты:

Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<CharSequence, Integer> output = transform(input, Integer::parseInt);

(есть также пример для карты значения, но это действительно надуманно, и я признаю, что наличие ограниченного подстановочного знака для Y помогает только в крайних случаях.)

второй момент заключается в том, что вместо запуска потока над входной картой entrySet, Я пробежал его по keySet. Это делает код немного чище, я думаю, за счет необходимости извлекать значения из карты, а не из записи карты. Кстати, у меня изначально было key -> key как первый аргумент к toMap() и это не удалось с ошибкой вывода типа для некоторая причина. Изменение его на (X key) -> key работал, как и Function.identity().

еще один вариант выглядит следующим образом:

static <X, Y, Z> Map<X, Z> transform1(Map<? extends X, ? extends Y> input,
                                      Function<Y, Z> function) {
    Map<X, Z> result = new HashMap<>();
    input.forEach((k, v) -> result.put(k, function.apply(v)));
    return result;
}

использует Map.forEach() вместо потоков. Это еще проще, я думаю, потому что он обходится без коллекционеров, которые несколько неуклюжи в использовании с картами. Причина в том, что Map.forEach() дает ключ и значение как отдельные параметры, тогда как поток имеет только одно значение - и вы должны выбрать, Использовать ли ключ или запись карты в качестве этого значение. С другой стороны, в этом нет богатой, струящейся доброты других подходов. : -)

общее решение, как так

public static <X, Y, Z> Map<X, Z> transform(Map<X, Y> input,
        Function<Y, Z> function) {
    return input
            .entrySet()
            .stream()
            .collect(
                    Collectors.toMap((entry) -> entry.getKey(),
                            (entry) -> function.apply(entry.getValue())));
}

пример

Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<String, Integer> output = transform(input,
            (val) -> Integer.parseInt(val));

это абсолютно должно быть 100% функциональным и свободно? Если нет, как насчет этого, что примерно так же коротко, как и получается:

Map<String, Integer> output = new HashMap<>();
input.forEach((k, v) -> output.put(k, Integer.valueOf(v));

(если вы можете жить с позором и виной объединения потоков с побочными эффектами)

мой StreamEx библиотека, которая улучшает стандартный поток API обеспечивает EntryStream класс, который лучше подходит для преобразования карты:

Map<String, Integer> output = EntryStream.of(input).mapValues(Integer::valueOf).toMap();

функция гуавы Maps.transformValues это то, что вы ищете, и это хорошо работает с лямбда-выражениями:

Maps.transformValues(originalMap, val -> ...)

Если вы не возражаете использовать сторонние библиотеки, мой Циклоп-реагировать lib имеет расширения для всех коллекция JDK типы, в том числе карта. Мы можем просто преобразовать карту непосредственно с помощью оператора ' map ' (по умолчанию карта действует на значения в карте).

   MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
                                .map(Integer::parseInt);

bimap может использоваться для преобразования ключей и значений одновременно

  MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
                               .bimap(this::newKey,Integer::parseInt);

альтернативой, которая всегда существует для целей обучения, является создание пользовательского коллектора через Collector.хотя tomap () JDK collector здесь лаконичен (+1 здесь).

Map<String,Integer> newMap = givenMap.
                entrySet().
                stream().collect(Collector.of
               ( ()-> new HashMap<String,Integer>(),
                       (mutableMap,entryItem)-> mutableMap.put(entryItem.getKey(),Integer.parseInt(entryItem.getValue())),
                       (map1,map2)->{ map1.putAll(map2); return map1;}
               ));

декларативным и более простым решением было бы:

yourMutableMap.replaceAll ((key, val) - > return_value_of_bi_your_function); Нотабене. помните, что вы изменяете свое состояние карты. Так что это может быть не то, что вы хотите.

Ура : http://www.deadcoderising.com/2017-02-14-java-8-declarative-ways-of-modifying-a-map-using-compute-merge-and-replace/

Comments

    Ничего не найдено.