0

I am still figuring out lambdas and I am wondering about one method.

private enum Result
{
    RESULT1,
    RESULT2,
    RESULT3
}
public static Map<String, Result> calculateResults(List<String> list)
{
    Map<String, Result> map = new HashMap<>(list.size());
    List<String> leavings = new ArrayList<>(list);

    map.putAll(leavings.stream().filter(Main::firstFilter).collect(Collectors.toMap(s -> s, s -> Result.RESULT1)));
    leavings.removeAll(map.keySet());

    map.putAll(leavings.stream().filter(Main::secondFilter).collect(Collectors.toMap(s -> s, s -> Result.RESULT2)));
    leavings.removeAll(map.keySet());

    map.putAll(leavings.stream().filter(Main::thirdFilter).collect(Collectors.toMap(s -> s, s -> Result.RESULT3)));
    leavings.removeAll(map.keySet());

    return map;
}
private static boolean firstFilter(String s)
{
    return s.length() == 5;
}
private static boolean secondFilter(String s)
{
    return s.contains("A");
}
private static boolean thirdFilter(String s)
{
    return BlockedStrings.getInstance().contains(s);
}

I think it would be super awesome if this could be made in some kind of the loop, but i have got no clue how to code it. Is it possible to assign filter Predicate to the enum variable or do anything else to not make it so repeatable?

5
  • 3
    Post the Main filters code, to make easy to understand the code. Commented Nov 1, 2016 at 18:00
  • 2
    Are you trying to group the list by a property of the elements? If so, take a look at Collectors.groupingBy() Commented Nov 1, 2016 at 18:03
  • 1
    Are you looking for leavings.removeIf(Main::firstFilter)? Commented Nov 1, 2016 at 18:05
  • 1
    Enums can have constructors, fields and member functions. Try adding a constructor after the list of enum values with a param for the predicate. Store the predicate in a field, change the list of values to RESULT1(Main::firstFilter), etc. and add a getPredicate function to the enum that returns the predicate stored by the enum's constructor. Commented Nov 1, 2016 at 18:07
  • Added methods. RemoveIf looks very usefull in this method, I will use it, though I am looking for a way to make it in the loop, give 3 methods in the array or assign them to enum Commented Nov 1, 2016 at 18:08

3 Answers 3

1

I’m not sure, whether I got your intention right, but to me, it looks like you want to do:

public static Map<String, Result> calculateResults(List<String> list) {
    return list.stream().filter(s -> firstFilter(s)||secondFilter(s)||thirdFilter(s))
        .collect(Collectors.toMap(s -> s, s -> firstFilter(s)? Result.RESULT1:
                              secondFilter(s)? Result.RESULT2: Result.RESULT3));
}

This has the disadvantage of evaluating the predicates multiple times. The alternative is to store the evaluation result into temporary objects:

public static Map<String, Result> calculateResults(List<String> list) {
    return list.stream().map(s -> new AbstractMap.SimpleImmutableEntry<>(s,
            firstFilter(s)? Result.RESULT1: secondFilter(s)? Result.RESULT2:
            thirdFilter(s)? Result.RESULT3: null))
        .filter(e -> e.getValue()!=null)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
Sign up to request clarification or add additional context in comments.

1 Comment

This won't work for rows that match none of the filters.
1

If you combine your filter methods into a single method that determines the status for each String, you can calculate the results as follows, without the need for the leavings list:

public static Map<String, Result> calculateResults(List<String> list)
{
    return list.stream().filter(s -> calculateResult(s) != null)
            .collect(Collectors.toMap(Function.identity(), Main::calculateResult));
}

private static Result calculateResult(String s)
{
    if (s.length() == 5) return Result.RESULT1;
    if (s.contains("A")) return Result.RESULT2;
    if (blockedStrings.contains(s)) return Result.RESULT3;
    return null;
}

6 Comments

@Holger exactly! That's why you need to filter them out before collecting to the map.
I see. It’s evaluating calculateResult twice. Well, the only alternative is storing intermediate results into objects. If toMap allowed null value, we could just store and remove nulls afterwards.
@Holger Yes. Could map to Optional<Result> instead but then you need to filter the map entries afterwards. list.stream() .collect(Collectors.toMap(Function.identity(), s -> Optional.ofNullable(calculateResult(s)))).entrySet() .stream().filter(e -> e.getValue().isPresent()) .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get())) Or bring in Guava.
Something in that direction. If null was allowed, you didn’t need to perform a second, filtering stream operation, but rather you could specify the HashMap::new supplier for the toMap operation, to ensure mutability and use collectingAndThen to perform a map.values().removeIf(Objects::isNull) afterwards. But with Optional, you have to change the Map’s formal type…
@Holger the final .get() changes back to the proper type.
|
0

You mean something like this?

List<Predicate<String>> predicates = Arrays.asList(
    Main::firstFilter, Main::secondFilter, Main::thirdFilter
);
Result[] results = Result.values();

for (int i = 0; i < predicates.size(); ++i) {
    final Predicate<String> predicate = predicates.get(i);
    final Result result = results[i];
    map.putAll(leavings.stream().filter(predicate)
             .collect(Collectors.toMap(s -> s, s -> result)));
    leavings.removeAll(map.keySet());
}

You could also incorporate the filter into the Result instances; or you could use a nice EnumMap<Result, Predicate<String>> to avoid the parallel array/list iteration.

Comments

Your Answer

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