Есть ли чистый (и нулевой безопасный) способ умножения значений карты в Java? - PullRequest
0 голосов
/ 17 октября 2018

У меня есть Map<String, Double>, и я хочу умножить все значения на карте, скажем, на 2, но сохранить нулевые значения как нулевые.

Я, очевидно, могу использовать цикл for для этого, но мне было интересно, есть ли более чистый способ сделать это?

Map<String, Double> someMap = someMapFunction();
Map<String, Double> adjustedMap = new Hashmap<>();
if (someMap != null) {
    for (Map.Entry<String,Double> pair : someMap.entryset()) {
        if (pair.getValue() == null) {
            adjustedMap.put(pair.getKey(), pair.getValue());
        } else {
            adjustedMap.put(pair.getKey(), pair.getValue()*2)
        }

    }
}

Также иногда карта, возвращаемая someMapFunction, являетсянеизменяемая карта, поэтому это не может быть сделано на месте с помощью Map.replaceAll.Я не мог придумать потоковое решение, которое было бы чище.

Ответы [ 12 ]

0 голосов
/ 17 октября 2018

Там уже много ответов.Некоторые из них кажутся мне немного сомнительными.В любом случае, большинство из них встраивают null -проверку в одной или другой форме.

Подход, который делает один шаг вверх по лестнице абстракции, следующий:

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

Это показано здесь, с тремя различными операторами (один из них Math::sin, в этом отношении):

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.UnaryOperator;

public class MapValueOps
{
    public static void main(String[] args)
    {
        Map<String, Double> map = new LinkedHashMap<String, Double>();
        map.put("A", 1.2);
        map.put("B", 2.3);
        map.put("C", null);
        map.put("D", 4.5);

        Map<String, Double> resultA = apply(map, nullSafe(d -> d * 2));
        System.out.println(resultA);

        Map<String, Double> resultB = apply(map, nullSafe(d -> d + 2));
        System.out.println(resultB);

        Map<String, Double> resultC = apply(map, nullSafe(Math::sin));
        System.out.println(resultC);

    }

    private static <T> UnaryOperator<T> nullSafe(UnaryOperator<T> op)
    {
        return t -> (t == null ? t : op.apply(t));
    }

    private static <K> Map<K, Double> apply(
        Map<K, Double> map, UnaryOperator<Double> op)
    {
        Map<K, Double> result = new LinkedHashMap<K, Double>();
        for (Entry<K, Double> entry : map.entrySet())
        {
            result.put(entry.getKey(), op.apply(entry.getValue()));
        }
        return result;
    }
}

Я думаю, что это чисто, потому что это хорошо разделяет проблемыприменения оператора и выполнения проверки null.И это null -безопасно, потому что ... имя метода говорит об этом.

(Можно поспорить, чтобы перетащить вызов, чтобы заключить оператора в nullSafe один в метод apply, но здесь дело не в этом)

Редактировать:

В зависимости от предполагаемого шаблона приложения, можно сделать что-то подобное и применить преобразование на месте , без создания новой карты, позвонив по номеру Map#replaceAll

0 голосов
/ 17 октября 2018

Еще один способ:

Map<String, Double> someMap = someMapFunction();

int capacity = (int) (someMap.size() * 4.0 / 3.0 + 1);
Map<String, Double> adjustedMap = new HashMap<>(capacity);

if (someMap != null) someMap.forEach((k, v) -> adjustedMap.put(k, v == null ? v : v * 2));

Обратите внимание, что я строю новую карту с коэффициентом загрузки по умолчанию (0.75 = 3.0 / 4.0) и начальной емкостью, всегда превышающей size * load_factor.Это гарантирует, что adjustedMap никогда не будет изменен / перефразирован.

0 голосов
/ 17 октября 2018

Если вы в порядке со значениями Optional, для вас могут работать следующие параметры:

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import static java.util.stream.Collectors.toMap;


public static Map<String, Optional<Double>> g(Map<String, Double> map, Function<Double, Double> f) {
    return map.entrySet().stream().collect(
            toMap(
                    e -> e.getKey(),
                    e -> e.getValue() == null ? Optional.empty() : Optional.of(f.apply(e.getValue()))
            ));
}

, а затем:

public static void main(String[] args) throws Exception {
    Map<String, Double> map = new HashMap<>();
    map.put("a", 2.0);
    map.put("b", null);
    map.put("c", 3.0);


    System.out.println(g(map, x -> x * 2));
    System.out.println(g(map, x -> Math.sin(x)));
}

отпечатки:

{a=Optional[4.0], b=Optional.empty, c=Optional[6.0]}
{a=Optional[0.9092974268256817], b=Optional.empty, c=Optional[0.1411200080598672]}

Это довольно чисто с созданием новой карты, делегированной Collectors, и дополнительным преимуществом типа возврата Map<String, Optional<Double>>, четко указывающим на возможность null s и поощряющим пользователей обрабатывать их.

0 голосов
/ 17 октября 2018

В качестве альтернативы потоковым и / или копирующим решениям в Google Guava существует *1005*:

Map<String, Double> adjustedMap = Maps.transformValues(someMap, value -> (value != null) ? (2 * value) : null);

служебный метод . Возвращает ленивыйвид исходной карты, которая сама по себе не выполняет никакой работы, но при необходимости применяет данную функцию.Это может быть как «за» (если вам вряд ли когда-нибудь понадобятся все значения, это сэкономит вам некоторое вычислительное время), так и «против» (если вам понадобится одно и то же значение много раз, или если вам потребуется дальнейшее изменение someMap без adjustedMap видя изменения) в зависимости от вашего использования.

0 голосов
/ 17 октября 2018

Как насчет этого?

Map<String, Double> adjustedMap = new HashMap<>(someMap);
adjustedMap.entrySet().forEach(x -> {
      if (x.getValue() != null) {
            x.setValue(x.getValue() * 2);
      }
});
0 голосов
/ 17 октября 2018

Вы можете сделать это с помощью этого кода:

Map<String, Double> map = new HashMap<>();
map.put("1", 3.0);
map.put("3", null);
map.put("2", 5.0);

Map<String, Double> res = 
map.entrySet()
   .stream()
   .collect(
           HashMap::new, 
           (m,v)->m.put(v.getKey(), v.getValue() != null ? v.getValue() * 2 : null),
           HashMap::putAll
           );

System.out.println(res);

, и результат будет:

{1 = 6,0, 2 = 10,0, 3 = ноль}

Это позволит вам сохранить null значения на карте.

0 голосов
/ 17 октября 2018

Чтобы сохранить нулевые значения, вы можете использовать что-то простое:

someMap.keySet()
        .stream()
        .forEach(key -> adjustedMap.put(key, (someMap.get(key)) == null ? null : someMap.get(key) * 2));

Редактировать в ответ на Комментарий Петра Янечека : вы можете применить предложенное ккопия someMap:

adjustedMap.putAll(someMap);
adjustedMap.keySet()
       .stream()
       .forEach(key -> adjustedMap.put(key, (adjustedMap.get(key)) == null ? null : adjustedMap.get(key) * 2));
0 голосов
/ 17 октября 2018

Попробуйте что-нибудь подобное с java 8 steam api

Map<String, Double> newMap = oldMap.entrySet().stream()
    .collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue() == null ? null: x.getValue()*2));
0 голосов
/ 17 октября 2018

Используйте вот так.

  Map<String, Double> adjustedMap = map.entrySet().stream().filter(x -> x.getValue() != null)
            .collect(Collectors.toMap(x -> x.getKey(), x -> 2*x.getValue()));
  //printing 
  adjustedMap.entrySet().stream().forEach(System.out::println);
0 голосов
/ 17 октября 2018

Это можно сделать следующим образом:

someMap.entrySet().stream()
            .filter(stringDoubleEntry -> stringDoubleEntry.getValue() != null) //filter null values out
            .forEach(stringDoubleEntry -> stringDoubleEntry.setValue(stringDoubleEntry.getValue() * 2)); //multiply values which are not null

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

...