【问题标题】:"Transpose" a multi-map to a list of maps将多地图“转置”到地图列表
【发布时间】:2016-08-15 13:40:57
【问题描述】:

假设我有一个这样的多地图:

MiddleName -> null
EmailAddress -> Toni@mymail.com, Mary@hermail.com, Paul@hismail.com
FirstName -> Toni, Mary, Paul
LastName -> Barry, null ,White
Id -> null

请注意每个“值”条目如何可以完全为空,或者包含与具有更多条目(我不知道是哪一个)相同数量的值,即使其中一些为空。

我希望它“转置”到这样的地图列表中:

MiddleName -> null
EmailAddress -> Toni@mymail.com
FirstName -> Toni
LastName -> Barry
Id -> null

MiddleName -> null
EmailAddress -> Mary@hermail
FirstName -> John
LastName -> null
Id -> null

MiddleName -> null
EmailAddress -> Paul@hismail.com
FirstName -> Paul
LastName -> White
Id -> null

我正在尝试使用 java8 流来执行此操作,但也可以以“旧”样式执行此操作。

搜索 stackoverflow 我发现了一个类似的问题 [1] 和其他一些相关的 [2,3,4],它们给了我一些想法,但没有完全适应。

现在,我确实实现了自己的解决方案,可以按我的意愿工作,但坦率地说,这可能是我多年来编写的最丑陋的代码......

List result = (...)
map.forEach(h -> {
    MultiValueMap<String, String> m = new LinkedMultiValueMap();
    h.entrySet().stream().forEach(e -> {
        String[] ss = e.getValue() == null ? null : e.getValue().toString().split(",");
        if (ss != null) {
            Arrays.asList(ss).stream().forEach(s -> m.add(e.getKey(), s));
        }
    });
    if (m.size() > 0) {
        int x = m.values().stream().max((o1, o2) -> o1.size() - o2.size()).get().size();
        for (int i = 0; i < x; i++) {
            Map<String, String> n = (Map) h.clone();
            Iterator<Map.Entry<String, String>> it = n.entrySet().iterator();
            while( it.hasNext()){
                Map.Entry<String, String> e = it.next();
                List<String> ss = m.get(e.getKey());
                if(ss!=null) {
                    e.setValue(ss.get(i));
                }
            }
            result.add(n);
        }
    }
});

第一遍只是将字符串拆分为一个数组,因为它最初是一个逗号分隔的字符串。然后我找到任何值中的最大元素数,我将条目中的所有值循环该次数,并为每个值创建一个带有结果的映射。坦率地说,我上周五写了这段代码,我还不能正确阅读它......

所以,起初这是一件简单的事情,但我最终陷入了困境,有没有更好的方法来做到这一点?

提前致谢。

[1]Java8 streams : Transpose map with values as list

[2]reversing keys/values - create new instance of HashMap

[3]"Transpose" a hashmap for key->value to value->key?

[4]Java invert map

【问题讨论】:

    标签: java java-8 java-stream


    【解决方案1】:

    这个怎么样(如果我理解正确的话):

        Multimap<String, String> map = ArrayListMultimap.create();
        map.put("MiddleName", null);
        map.putAll("EmailAddress", ImmutableList.of("toni@gmail.com", "mary@gmail.com", "paul@gmail.com"));
    
        // that's the key with the "biggest" collection within the map
        int biggest = map.asMap().entrySet().stream().collect(Collectors.reducing(0, entry -> entry.getValue().size(), Integer::max));
    
        Multimap<String, String> newMap = ArrayListMultimap.create();
    
        // "padding" the collection when required
        map.keySet().stream().forEach(key -> {
            int currentSize = map.get(key).size();
            newMap.putAll(key, map.get(key));
            if (currentSize < biggest) {
                newMap.putAll(key, Collections.nCopies(biggest - currentSize, (String) null));
            }
        });
    
        System.out.println(newMap); // {MiddleName=[null, null, null], EmailAddress=[toni@gmail.com, mary@gmail.com, paul@gmail.com]}
    }
    

    从这里映射到某个 Person 对象相当容易。

    【讨论】:

      【解决方案2】:

      坦率地说,我首先会尽量避免陷入这种情况,并开始使用真实对象而不是地图(即使您想要的地图列表也应该是List&lt;Person&gt;),但我会这样做:

      Map<String, List<String>> multiMap = ...;
      List<Map<String, String>> result = new ArrayList<>();
      
      // find an entry with a non-null value, and get the size of the
      // list
      OptionalInt sizeOfLists =
          multiMap.values()
                  .stream()
                  .filter(Objects::nonNull)
                  .mapToInt(List::size)
                  .findAny();
      
      // for each index, create a person and put each key and the
      // corresponding value at that index in that map
      sizeOfLists.ifPresent(size -> {
          for (int i = 0; i < size; i++) {
              int index = i;
              Map<String, String> person = new HashMap<>();
              result.add(person);
              multiMap.entrySet()
                      .stream()
                      .filter(entry -> entry.getValue() != null)
                      .forEach(entry -> person.put(entry.getKey(), entry.getValue().get(index)));
          }
      });
      

      请注意,您的代码并没有那么糟糕。但是,如果你给变量取有意义的名字而不是hmess,它会更易读。

      【讨论】:

      • 您好,感谢您的回复。不幸的是,我无法避免这种情况,因为这是更大的操作流程的一部分。您的代码可读性很强,我只要看一下就可以理解。我要尝试一下,因为我的实际情况比这要复杂一些。当然,关于我的变量命名你是对的...... :) 我会让你知道它是怎么回事。干杯。
      猜你喜欢
      • 1970-01-01
      • 2020-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-31
      相关资源
      最近更新 更多