【问题标题】:Partitioning a Map in Java 8+在 Java 8+ 中对地图进行分区
【发布时间】:2019-06-30 04:33:24
【问题描述】:

我有一个Map<String, String> 和一个List<String>。我想根据条件对Map 进行分区

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

并产生两个Map(s)。这样做最优雅的方式是什么?我在 Java 11 上,所以你可以在答案中抛出你想要的一切。

我现在想到的是:

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

但这给出了Map<Boolean, List<Entry<String, String>>>

【问题讨论】:

  • 你能举一个真实数据的例子吗?你想实现什么?而且我不一定要在这里全流:一个流创建一个结果,但你想要两张地图?!
  • 查看相关的 stackoverflow.com/questions/28856781。最好的方法取决于地图和列表的大小,以及应该如何使用结果。例如,Guava Maps 可以简单快速地在原始地图上提供 2 个过滤视图以避免额外的内存使用,但如果在结果中多次调用 contains(),则性能可能会非常差。

标签: java java-stream


【解决方案1】:

您不能真正使用流生成两个单独的地图(至少不是以最优雅的方式)。不要害怕使用旧的常规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());
    }
}

【讨论】:

    【解决方案2】:

    您可以使用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}}

    【讨论】:

      【解决方案3】:

      您可以通过对原始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 进行比较,以提取filteredMap 中不存在的条目。

      【讨论】:

      • 这种方法的性能很糟糕 O(n * m),当对于 HashMaps 和输入时,给定 get() 和 contains() 有 O(1),O(m) 的解决方案是可能的。请参阅 stackoverflow.com/questions/28856781
      【解决方案4】:

      作为@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);
      

      【讨论】:

        【解决方案5】:

        尽管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);
        

        【讨论】:

        • 我猜使用 stream().filter( ... ).collect( ... ) 会是一样的吗?
        • @Asoub 确实如此。达山在这里的回答建议。
        【解决方案6】:

        您可以迭代地图并使用 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 映射,其中包含两个条目,每个分区一个。这些值为LinkedHashMaps,以便保留插入顺序。

        然后,我们从列表中创建一个HashSet,因此调用set.contains(k) 是一个O(1) 操作(否则,如果我们执行list.contains(k),则对于地图的每个条目,这将是O(n),因此产生的总时间复杂度为O(n^2),这很糟糕)。

        最后,我们迭代输入映射并将(k, v)条目放入对应的分区,根据调用set.contains(k)的结果。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-05-30
          • 2016-08-28
          • 1970-01-01
          • 1970-01-01
          • 2017-07-02
          相关资源
          最近更新 更多