简体   繁体   中英

Java-8 Streams: Convert Map<String, List<List<DataType>>> to Map<String, List<DataType>>

I've just started looking at Java 8 and trying out lambdas, below is the problem i wanted to solve

Below is the code so far I have

    Map<String, List<List<DataType>>> a = Arrays.stream(OperatorType.values())
            .collect(
                    groupingBy(i -> i.getKey(), 
                            mapping(i -> i.getSupportedtypes(), 
                                    Collectors.toList()))); 

The above snippet works, but thats not what i want. I want to it to be returning Map<String, List<DataType>>

I am guessing some way of flat mapping will resolve the question

OperatorType enum for reference

 public enum OperatorType {

IS_ONE_OF("IS_ONE_OF", 1,
        Collections.singletonList(DataType.ALL)),
IS_NOT_ONE_OF("IS_NOT_ONE_OF", 2,
        Collections.singletonList(DataType.ALL)),
ENDS_WITH("ENDS_WITH", 3,
        Collections.singletonList(DataType.STRING)),
DOES_NOT_ENDS_WITH("DOES_NOT_ENDS_WITH", 4,
        Collections.singletonList(DataType.STRING)),
STARTS_WITH("STARTS_WITH", 5,
        Collections.singletonList(DataType.STRING)),
DOES_NOT_START_WITH("DOES_NOT_START_WITH", 6,
        Collections.singletonList(DataType.STRING)),
MATCHES("MATCHES", 7,
        Collections.singletonList(DataType.STRING)),
DOES_NOT_MATCH("DOES_NOT_MATCH", 8,
        Collections.singletonList(DataType.STRING)),
CONTAINS("CONTAINS", 9,
        Collections.singletonList(DataType.STRING)),
DOES_NOT_CONTAIN("DOES_NOT_CONTAIN", 10,
        Collections.singletonList(DataType.STRING)),
GREATER_THAN("GREATER_THAN", 11, Arrays.asList(DataType.INTEGER,DataType.DOUBLE)), 
GREATER_THAN_OR_EQUAL_TO("GREATER_THAN_OR_EQUAL_TO", 12, Arrays.asList(DataType.INTEGER,DataType.DOUBLE)), 
LESS_THAN("LESS_THAN", 13, Arrays.asList(DataType.INTEGER,DataType.DOUBLE)),
LESS_THAN_OR_EQUAL_TO("LESS_THAN_OR_EQUAL_TO", 15, Arrays.asList(DataType.INTEGER,DataType.DOUBLE)), 
AFTER("AFTER", 15,
        Collections.singletonList(DataType.DATE)),
BEFORE("BEFORE", 16,
        Collections.singletonList(DataType.DATE));

private final int value;

private final String key;

private final List<DataType> supportedtypes;

OperatorType(String key, int value, List<DataType> supportedtypes) {
    this.value = value;
    this.key = key;
    this.supportedtypes = supportedtypes;
}

public int getValue() {
    return this.value;
}

public String getKey() {
    return this.key;
}

public List<DataType> getSupportedtypes() {
    return this.supportedtypes;
}

@Override
public String toString() {
    return String.valueOf(this.value);
}

@JsonCreator
public static OperatorType create(String key) {
    if (key == null) {
        throw new IllegalArgumentException();
    }
    for (OperatorType v : values()) {
        if (v.getKey().equalsIgnoreCase(key)) {
            return v;
        }
    }
    throw new IllegalArgumentException();
}

public static OperatorType fromValue(Integer value) {

    for (OperatorType type : OperatorType.values()) {
        if (value == type.getValue()) {
            return type;
        }
    }
    throw new IllegalArgumentException("Invalid enum type supplied");
}

public static OperatorType fromValue(String key) {

    for (OperatorType type : OperatorType.values()) {
        if (type.getKey().equalsIgnoreCase(key)) {
            return type;
        }
    }
    throw new IllegalArgumentException("Invalid enum type supplied");
}

}

Found one way to do this:

public static Map<DataType, List<OperatorType>> buildmap() {
    Map<OperatorType, List<DataType>> map = new HashMap<>();

    Arrays.stream(OperatorType.values()).collect(groupingBy(i -> OperatorType.fromValue(i.getKey()),
            mapping(i -> i.getSupportedtypes(), Collectors.toList()))).entrySet().forEach(entry ->
                {
                    map.put(entry.getKey(),
                            entry.getValue().stream().flatMap(List::stream).collect(Collectors.toList()));
                });

    return map.entrySet().stream().flatMap(e -> e.getValue().stream().map(v -> new SimpleEntry<>(v, e.getKey())))
            .collect(
                    Collectors.groupingBy(Entry::getKey, Collectors.mapping(Entry::getValue, Collectors.toList())));
}

I've thrown out a few methods and switched out your DataType with Integer in your enum, it now looks like this:

enum OperatorType {
    IS_ONE_OF("IS_ONE_OF", 1,
           Collections.singletonList(1)),
    IS_NOT_ONE_OF("IS_NOT_ONE_OF", 2,
           Collections.singletonList(1)),
    ENDS_WITH("ENDS_WITH", 3,
           Collections.singletonList(2)),
    DOES_NOT_ENDS_WITH("DOES_NOT_ENDS_WITH", 4,
           Collections.singletonList(2)),
    STARTS_WITH("STARTS_WITH", 5,
           Collections.singletonList(2)),
    DOES_NOT_START_WITH("DOES_NOT_START_WITH", 6,
           Collections.singletonList(2)),
    MATCHES("MATCHES", 7,
           Collections.singletonList(2)),
    DOES_NOT_MATCH("DOES_NOT_MATCH", 8,
           Collections.singletonList(2)),
    CONTAINS("CONTAINS", 9,
           Collections.singletonList(2)),
    DOES_NOT_CONTAIN("DOES_NOT_CONTAIN", 10,
           Collections.singletonList(2)),
    GREATER_THAN("GREATER_THAN", 11, Arrays.asList(3,4)), 
    GREATER_THAN_OR_EQUAL_TO("GREATER_THAN_OR_EQUAL_TO", 12, Arrays.asList(3,4)), 
    LESS_THAN("LESS_THAN", 13, Arrays.asList(3,4)),
    LESS_THAN_OR_EQUAL_TO("LESS_THAN_OR_EQUAL_TO", 15, Arrays.asList(3,4)), 
    AFTER("AFTER", 15,
           Collections.singletonList(5)),
    BEFORE("BEFORE", 16,
           Collections.singletonList(5));

    private final int value;

    private final String key;

    private final List<Integer> supportedtypes;

    OperatorType(String key, int value, List<Integer> supportedtypes) {
       this.value = value;
       this.key = key;
       this.supportedtypes = supportedtypes;
    }

    public int getValue() {
       return this.value;
    }

    public String getKey() {
       return this.key;
    }

    public List<Integer> getSupportedtypes() {
       return this.supportedtypes;
    }

    @Override
    public String toString() {
       return String.valueOf(this.value);
    }
}

Should be fairly straightforward to switch your DataType back into the following code:

Map<String,List<Integer>> map = Arrays.stream(OperatorType.values()).collect(Collectors.toMap(OperatorType::getKey, OperatorType::getSupportedtypes));
map.forEach((k,v) -> System.out.println(k + " " + v));
System.out.println("");

Map<Integer,List<OperatorType>> map2 = 
        map.entrySet().stream()
        .flatMap(e -> e.getValue().stream().map(f -> new AbstractMap.SimpleEntry<>(f,e.getKey())))
        .collect(Collectors.groupingBy(r -> r.getKey(), Collectors.mapping(s -> Enum.valueOf(OperatorType.class, s.getValue()), Collectors.toList())));
map2.forEach((k,v) -> System.out.println(k + " " + v));

You can chain these two steps. But for readibility, I've left them separate.

For me the print statements print the following:

DOES_NOT_CONTAIN [2]
STARTS_WITH [2]
LESS_THAN_OR_EQUAL_TO [3, 4]
DOES_NOT_MATCH [2]
AFTER [5]
DOES_NOT_ENDS_WITH [2]
IS_ONE_OF [1]
LESS_THAN [3, 4]
GREATER_THAN_OR_EQUAL_TO [3, 4]
CONTAINS [2]
DOES_NOT_START_WITH [2]
IS_NOT_ONE_OF [1]
BEFORE [5]
GREATER_THAN [3, 4]
ENDS_WITH [2]
MATCHES [2]

1 [2, 1]
2 [7, 3, 6, 9, 4, 8, 5, 10]
3 [11, 12, 13, 15]
4 [11, 12, 13, 15]
5 [16, 15]

I know this answer is lacking some explanation, but it produces the Map<DataType,List<OperatorType>> that you want

It looks like your keys are identical to the enum name. If that's the case:

a) You don't need the key at all; just use the enum constant's name:

public String getKey() {
    return this.name();
}

b) Since the keys are unique, there's no grouping necessary. Use the toMap() collector instead:

Map<String, List<DataType>> a = Arrays.stream(OperatorType.values())
        .collect(toMap(OperatorType::getKey, OperatorType::getSupportedtypes));

You should be able to use Stream#flatMap for this:

Map<String, List<DataType>> map = new HashMap<>();

a.entrySet().forEach(entry -> {
    map.put(entry.getKey(), entry.getValue().stream()
                                            .flatMap(List::stream)
                                            .collect(Collectors.toList()));
});

Because the List of List s in each value of the Map must be condensed, we must first make a new Map and then re-add the elements after condensing each List<List<DataType>>

One way could be to replace the collector toList to a more appropriate one, eg reducing :

Map<String, List<DataType>> a = Arrays.stream(OperatorType.values())
  .collect(
    groupingBy(i -> i.getKey(), 
      mapping(i1 -> i1.getSupportedtypes(),
        reducing((List<DataType>) new ArrayList<DataType>(), (l,r) -> {
          List<DataType> list = new ArrayList<DataType>();
          list.addAll(l);
          list.addAll(r);
          return list;
        })))); 

For better readability it might be helpful to extract the binary operator function, eg (l,r) -> join(l,r) , where join concatenates two lists.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM