将多地图“转置”为地图列表

时间:2016-08-15 13:40:58

标签: java java-8 java-stream

假设我有这样的多地图:

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

注意每个“values”条目如何可以完全为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

2 个答案:

答案 0 :(得分:0)

我坦率地试图避免首先处于这种情况,并开始使用真实对象而不是地图(即使你想要的地图列表应该是List<Person>),但我会这样做这样:

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,那么它将更具可读性。

答案 1 :(得分:0)

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

BAPI_SFLIGHT_GETLIST
*"*"Lokale Schnittstelle:
*"  IMPORTING
*"     VALUE(FROMCOUNTRYKEY) LIKE  BAPISFDETA-COUNTRYFR
*"     VALUE(FROMCITY) LIKE  BAPISFDETA-CITYFROM
*"     VALUE(TOCOUNTRYKEY) LIKE  BAPISFDETA-COUNTRYTO
*"     VALUE(TOCITY) LIKE  BAPISFDETA-CITYTO
*"     VALUE(AIRLINECARRIER) LIKE  BAPISFDETA-CARRID DEFAULT SPACE
*"     VALUE(AFTERNOON) LIKE  BAPI_AUX-AFTERNOON DEFAULT SPACE
*"     VALUE(MAXREAD) LIKE  BAPI_AUX-MAXREAD DEFAULT 0
*"  EXPORTING
*"     VALUE(RETURN) LIKE  BAPIRET2 STRUCTURE  BAPIRET2
*"  TABLES
*"      FLIGHTLIST STRUCTURE  BAPISFLIST

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