#java #hashmap #java-8 #java-stream
#java #hashmap #java-8 #java-stream
Вопрос:
У меня есть две хэш-карты в форме
Map<Integer, Map<String,Double>> MAP_1
Map<Integer, Map<String,Double>> MAP_2
Пусть INNER будет внутренней картой, т.е.
Map<String,Double>
Для каждой пары соответствующих ключей я хочу выполнить операцию (например, вычитание) между двумя внутренними картами. В свою очередь, эта операция должна выполняться между двумя соответствующими ключами внутренних карт.
Результирующая карта должна быть
Map<Integer,List<Double> RESULT
Примеры карт
MAP_1
------------------------------
| | 2016-10-02 10.0 |
| ID1 | 2016-10-03 20.0 |
| | 2016-10-04 30.0 |
------------------------------
| | 2016-10-02 1.00 |
| ID2 | 2016-10-03 2.00 |
| | 2016-10-04 3.00 |
------------------------------
MAP_2
------------------------------
| | 2016-10-02 2.00 |
| ID1 | 2016-10-03 3.00 |
| | |
------------------------------
| | 2016-10-02 1.00 |
| ID3 | 2016-10-03 2.00 |
| | 2016-10-04 3.00 |
------------------------------
RESULT
---------------
| | 8.00 |
| ID1 | 17.0 |
| | |
---------------
Итак, что касается внешних карт, необходимо учитывать только ID1; в свою очередь, операция должна включать внутренние (общие) ключи «2016-10-02» и «2016-10-03».
Вот мой текущий код
Set<Integer> common_keys = new LinkedHashSet<Integer>(map1.keySet());
common_keys.retainAll(map2.keySet());
System.out.println("Common keys: " common_keys.toString());
map1.forEach((k,v) -> {
Map<String,Double> submap_1 = new LinkedHashMap<String,Double>(v);
Map<String,Double> submap_2 = new LinkedHashMap<String,Double>(map2.get(k));
Set<String> commons = new LinkedHashSet<String>(submap_1.keySet());
commons.retainAll(submap_2.keySet());
System.out.println(k " : common days: " commons);
List<Double> val1 = submap_1.keySet()
.stream()
.filter(c -> commons.contains(c))
.map(c -> submap_1.get(c))
.collect(Collectors.toList());
List<Double> val2 = submap_2.keySet()
.stream()
.filter(c -> commons.contains(c))
.map(c -> submap_2.get(c))
.collect(Collectors.toList());
List<Double> ABS = IntStream.range(0, val1.size())
.mapToObj(i -> val1.get(i) - val2.get(i))
.collect(Collectors.toList());
diff_abs.put(k, ABS);
});
Есть ли более простой и разумный способ сделать это, используя потоковый API JAVA 8?
Заранее спасибо
Комментарии:
1. Почему так хочется использовать stream API? Если вам подходит обычный код, просто используйте обычный код.
2. Я уже использую stream API в своем коде. Мне было интересно, существует ли более компактный способ достижения того же результата
Ответ №1:
Создание Set
не требуется. Замените это проверкой null
in для того же ключа в другой карте. Кроме того, создание копий карт не требуется, поскольку карты никогда не изменяются в методе.
public static <T, U> Map<T, List<Double>> merge(Map<T, Map<U, Double>> m1, Map<T, Map<U, Double>> m2) {
Map<T, List<Double>> result = new HashMap<>();
m1.forEach((k, v) -> {
Map<U, Double> v2 = m2.get(k);
if (v2 != null) {
// both outer maps contain the same key
ArrayList<Double> list = new ArrayList<>();
v.forEach((innerK, innerV) -> {
Double d = v2.get(innerK);
if (d != null) {
// matching key in both inner maps
list.add(innerV - d);
}
});
// list.trimToSize();
result.put(k, list);
}
});
return resu<
}
Ответ №2:
Вы можете создать повторно используемый метод для объединения двух карт, сохранив только общие ключи и применив функцию к обоим значениям:
public static <K,V,R> Map<K, R> merge(Map<K,V> map1, Map<K,V> map2, BiFunction<V,V,R> f) {
boolean hasOrder=map1.entrySet().spliterator().hasCharacteristics(Spliterator.ORDERED);
return map1.entrySet().stream()
.collect(hasOrder? LinkedHashMap<K,R>::new: HashMap<K,R>::new, (m,e)-> {
V v2 = map2.get(e.getKey());
if(v2!=null) m.put(e.getKey(), f.apply(e.getValue(), v2));
}, Map::putAll);
}
Затем вы можете легко реализовать слияние вложенных карт, используя метод дважды:
public static <K1, K2> Map<K1, List<Double>> merge(
Map<K1, Map<K2, Double>> map1, Map<K1, Map<K2, Double>> map2) {
return merge(map1, map2,
(a,b) -> new ArrayList<>(merge(a, b, (x,y) -> x-y).values()));
}
Обратите внимание, что приведенный выше код достаточно умен, чтобы создать LinkedHashMap
, если и только если, первая карта имеет внутренний порядок, например, сама по себе является LinkedHashMap
или SortedMap
, и простое HashMap
иначе. Порядок второй карты в любом случае не сохраняется, что не является проблемой, если, как, по-видимому, предполагает ваш исходный код, обе карты имеют одинаковый порядок. Если их порядок отличается, очевидно, что нет способа сохранить оба порядка.
Ответ №3:
Всего несколько моментов в вашем примере выше. Мои переходные реализации для List
, Set
, и Map
являются ArrayList
, HashSet
, и HashMap
, если мне не нужно что-то специально предоставленное их Linked
реализациями, например, согласованный порядок (но это случается чрезвычайно редко).
Вот как я бы подошел к этому:
public static <T, S> Map<T, List<Double>> method(
Map<T, Map<S, Double>> map1,
Map<T, Map<S, Double>> map2)
{
Set<T> commonKeys = intersection(map1.keySet(), map2.keySet());
return commonKeys.stream().collect(Collectors.toMap(
Function.identity(),
key -> {
Map<S, Double> inner1 = map1.get(key);
Map<S, Double> inner2 = map2.get(key);
Set<S> innerCommonKeys = intersection(
inner1.keySet(),
inner2.keySet());
return innerCommonKeys.stream().map(innerKey ->
inner1.get(innerKey) - inner2.get(innerKey))
.collect(Collectors.toList());
}));
}
private static <T> Set<T> intersection(Set<T> set1, Set<T> set2) {
Set<T> intersection = new HashSet<T>(set1);
intersection.retainAll(set2);
return intersection;
}
И вот «тест», чтобы показать, что он работает:
public static void main(String[] args)
{
Map<String, Map<String, Double>> map1 = new HashMap<>();
Map<String, Double> inner11 = new HashMap<>();
inner11.put("2016-10-02", 10.0);
inner11.put("2016-10-03", 20.0);
inner11.put("2016-10-04", 30.0);
map1.put("ID1", inner11);
Map<String, Double> inner12 = new HashMap<>();
inner12.put("2016-10-02", 1.00);
inner12.put("2016-10-03", 2.00);
inner12.put("2016-10-04", 3.00);
map1.put("ID2", inner12);
Map<String, Map<String, Double>> map2 = new HashMap<>();
Map<String, Double> inner21 = new HashMap<>();
inner21.put("2016-10-02", 2.00);
inner21.put("2016-10-03", 3.00);
map2.put("ID1", inner21);
Map<String, Double> inner22 = new HashMap<>();
inner22.put("2016-10-02", 1.00);
inner22.put("2016-10-03", 2.00);
inner22.put("2016-10-04", 3.00);
map2.put("ID3", inner22);
System.out.println(method(map1, map2));
}
Вывод: {ID1=[8.0, 17.0]}
Комментарии:
1. Иногда в моем приложении мне нужно, чтобы обработка не нарушала порядок ключей; более того, добавление пар (ключ, значение) в хэш-карту должно приводить к добавлению новых ключей. По этой причине я использовал
Linked
реализацию.