2

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())));
}
11
  • Why are you using groupingBy? The "keys" in your enum are all unique Commented May 1, 2017 at 14:19
  • I wanted to create a reverse lookup map from the above enum. i.e Map<DataType, List<OperatorType>> , so i thought I create a Map<Operator, List<DataType>>, so that i can achieve the desired result Commented May 1, 2017 at 14:21
  • If I understand it correctly, you can use toMap instead of groupingBy Commented May 1, 2017 at 14:29
  • I am not sure how toMap can be used in this case as this involves list modification i.e List<DataType> Commented May 1, 2017 at 14:31
  • @RobinTopper yes please that would be great. Also please have a look at my approach. Added in the question itself Commented May 1, 2017 at 16:20

4 Answers 4

2

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

Sign up to request clarification or add additional context in comments.

Comments

1

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

Comments

0

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 Lists 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>>

3 Comments

is there way not to create the map and achieve this ?
By that, do you mean collect it directly into a Map<String, List<DataType>>?
If you use Collectors#toMap, it returns a Map<Object, Object> which would have to be explicitly cast to Map<String, List<Integer>>, which is messy. You could define the reduction generic types, but that would be even messier. Most likely, your best bet would be to use the method above in my answer.
0

One way could be to replace the collector toList to a more appropriate one, e.g. 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, e.g. (l,r) -> join(l,r), where join concatenates two lists.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.