sdb
sdb

Reputation: 347

Java 8 summarise by multiple predicate

Consider the following pojo class annotated using Lombok annotations

@Setter
@Getter
@Builder
@ToString
public class User {
    private String firstName;
    private String lastName;
    private Gender gender;
    private Integer age;
    private Integer points;
}

requirement is to get LongSummaryStatistics of 'points' attribute for following predicates:

  1. Predicate<User> adultMenPredicate = user -> Gender.MALE == user.getGender && user.getAge()>18
  2. Predicate<User> adultWomenPredicate = user -> Gender.FEMALE == user.getGender && user.getAge()>18
  3. Predicate<User> minorPredicate = user -> user.getAge()<18

My current Implementation is :

private LongSummaryStatistics getPointStats(List<User> users, Predicate<User> predicate) {
    return users.stream().filter(predicate).mapToLong(User::getPoints).summaryStatistics();
}

System.out.println("point stats for adult men: " + getPointStats(users, adultMenPredicate));
System.out.println("point stats for adult women: " + getPointStats(users, adultWomenPredicate));
System.out.println("point stats for minors: " + getPointStats(users, minorPredicate));

Here we are iterating the users collection thrice. Is it possible to get this in just one iteration ?

Upvotes: 0

Views: 466

Answers (1)

g-t
g-t

Reputation: 1533

I've figured out something like that:

public static void main(String [] args) {
    List<User> users = ImmutableList.of(new User("a", "s", MALE, 19, 22),
                                        new User("a", "s", MALE, 15, 49),
                                        new User("a", "s", MALE, 22, 11),
                                        new User("a", "s", FEMALE, 19, 1),
                                        new User("a", "s", MALE, 12, 22));

    Map<Type, Integer> collect = users.stream()
            .map(u -> Tuple.tuple(u, resolveType(u)))
            .collect(Collectors.groupingBy(Tuple::right, Collectors.summingInt(t -> t.left().points)));
    System.out.println(collect);
}

public static Type resolveType(final User user) {
    if (user.gender == MALE && user.age > 18) {
        return Type.ADULT_MALE;
    } else if (user.gender == FEMALE && user.age > 18) {
        return Type.ADULT_FEMALE;
    } else {
        return Type.MINOR;
    }
}

public enum Type {
    ADULT_MALE, ADULT_FEMALE, MINOR
}

I guess it's a balanced solution - quite efficient and readable. I don't like if-else statements so you can replace it with Map like:

private static final Map<Predicate<User>, Type> predicates = ImmutableMap.of(
        user -> user.getGender() == MALE && user.getAge() >= 18, Type.ADULT_MALE,
        user -> user.getGender() == FEMALE && user.getAge() >= 18, Type.ADULT_FEMALE,
        user -> user.getAge() < 18, Type.MINOR
);

public static Type resolveType(final User user) {
    return predicates.entrySet().stream()
            .filter(entry -> entry.getKey().test(user))
            .findFirst()
            .map(Map.Entry::getValue)
            .orElseThrow(RuntimeException::new);
}

It prints:

{ADULT_MALE=33, MINOR=71, ADULT_FEMALE=1}

I guess you don't have to worry about performance unless you're dealing with huge collections.

// edit Just to make it clear. My tuple implementation looks like that:

@ToString
@EqualsAndHashCode
public class Tuple<L, R> {
    public static <L, R> Tuple<L, R> tuple(L left, R right) {
        return new Tuple<>(left, right);
    }

    private final L left;
    private final R right;

    private Tuple(L left, R right) {
        this.left = left;
        this.right = right;
    }

    public L left() {
        return left;
    }

    public R right() {
        return right;
    }
}

Upvotes: 2

Related Questions