Разделение карты в Java 8+

У меня есть Map<String, String> и List<String>. Я хотел бы разделить Map на основе условий

foreach(map.key -> list.contains(map.key))

и создайте две Map. Какой самый элегантный способ сделать это? Я на Java 11, так что вы можете бросить все, что вы хотите в ответах.

То, к чему я пришел сейчас, это:

map.entrySet()
   .stream()
   .collect(partitioningBy(e -> list.contains(o.getKey())));

но это дает Map<Boolean, List<Entry<String, String>>>.

Ответ 1

Вы можете уменьшить каждую группу, используя toMap (в качестве нижестоящего коллектора):

Map<String, String> myMap = new HashMap<>();
myMap.put("d", "D");
myMap.put("c", "C");
myMap.put("b", "B");
myMap.put("A", "A");

List<String> myList = Arrays.asList("a", "b", "c");

Map<Boolean, Map<String, String>> result = myMap.entrySet()
        .stream()
        .collect(Collectors.partitioningBy(
                            entry -> myList.contains(entry.getKey()),
                            Collectors.toMap(Entry::getKey, Entry::getValue)
                    )
        );

И для этого примера, который производит {false={A=A, d=D}, true={b=B, c=C}}

Ответ 2

Хотя partitioningBy - это путь, когда вам нужны обе альтернативы в качестве выходных данных, основанных на условии. Еще один выход (полезный для создания карты на основе одного условия) - использовать Collectors.filtering как:

Map<String, String> myMap = Map.of("d", "D","c", "C","b", "B","A", "A");
List<String> myList = List.of("a", "b", "c");
Predicate<String> condition = myList::contains;

Map<String, String> keysPresentInList = myMap.keySet()
        .stream()
        .collect(Collectors.filtering(condition,
                Collectors.toMap(Function.identity(), myMap::get)));
Map<String, String> keysNotPresentInList = myMap.keySet()
        .stream()
        .collect(Collectors.filtering(Predicate.not(condition),
                Collectors.toMap(Function.identity(), myMap::get)));

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

myMap.keySet().retainAll(myList);

Ответ 3

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

Map<String, String> contains = new HashMap<>();
Map<String, String> containsNot = new HashMap<>();

for(Map.Entry<String, String> entry : yourMap.entrySet()) {
    if (yourList.contains(entry.getKey())) {
        contains.put(entry.getKey(), entry.getValue());
    } else {
        containsNot.put(entry.getKey(), entry.getValue());
    }
}

Ответ 4

Вы можете отфильтровать map, применив фильтрацию к исходной map, например:

List<String> list = new ArrayList<>(); //List of values
Map<String, String> map = new HashMap<>();

Map<String, String> filteredMap = map.entrySet()
.stream()
.filter(e -> list.contains(e.getKey()))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));

Затем вы можете сравнить содержимое filteredMap map с исходной map чтобы извлечь записи, которых нет в filteredMap map.

Ответ 5

Вы можете перебрать карту и использовать вкусности, представленные в Java 8+:

Map<Boolean, Map<String, String>> result = Map.of(true, new LinkedHashMap<>(), 
                                                  false, new LinkedHashMap<>());
Set<String> set = new HashSet<>(list);
map.forEach((k, v) -> result.get(set.contains(k)).put(k, v));

Сначала мы создаем карту result с двумя записями, по одной для каждого раздела. Значения LinkedHashMap так что порядок вставки сохраняется.

Затем мы создаем HashSet из списка, так что set.contains(k) является операцией O(1) (в противном случае, если бы мы сделали list.contains(k), это будет O(n) для каждой записи карта, таким образом, получая общую временную сложность O(n^2), что плохо).

Наконец, мы перебираем входную карту и помещаем запись (k, v) в соответствующий раздел согласно результату вызова set.contains(k).

Ответ 6

В качестве дополнения к ответу @ernest_k вы можете использовать функцию groupingBy:

Map<String, String> myMap = new HashMap<>();
myMap.put("d", "D");
myMap.put("c", "C");
myMap.put("b", "B");
myMap.put("A", "A");
List<String> myList = Arrays.asList("a", "b", "c");

Function<Entry<String, String> , Boolean> myCondition =  i -> myList.contains(i.getKey());

Map<Boolean,List<Entry<String, String>>>  myPartedMap = myMap.entrySet()
        .stream().collect(Collectors.groupingBy(myCondition));

System.out.println(myPartedMap);