user2116243
user2116243

Reputation: 303

Filtering with Java 8: Map<String, Set<Object>> from Map<String, Set<Object>> based on an attribute of Object

This is class Item.

public class Item {
    String id;
    String name;
    Integer value;
    Boolean status;
}

I have a Map(String, Set(Item)). I want to write a method that returns a Map(String, Set(Item)) such that only Items with status = false or status = null are present in the resulting map. I don't want a set-wide operation. I want the resulting subsets to only contain those Item that have status == Boolean.FALSE OR status == null. I don't want the entire set to get included or excluded. I only want those individual items included or excluded as per the status value.

Here's what I've tried so far.

public Map<String,Set<Item>> filterByStatus(Map<String, Set<Item>> changes) {
    return changes.entrySet()
                  .stream()
                  .filter(p -> p.getValue()
                                .stream()
                                .anyMatch(item -> BooleanUtils.isNotTrue(item.isStatus())))
                  .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

It didn't work! I get back the same results as I would if I didn't call filterByStatus.

UPDATE

public Map<String,Set<Item>> filterByStatus(Map<String, Set<Item>> changes) {
    return changes.entrySet()
                  .stream()
                  .map(p -> p.getValue()
                                .stream()
                                .filter(item -> BooleanUtils.isNotTrue(item.isStatus())))
                  .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

Result: There's an error in the collect(Collectors.toMap()) line saying Non-static method cannot be referenced from static context.

Upvotes: 2

Views: 1510

Answers (4)

Holger
Holger

Reputation: 298153

Alternatively to a Stream solution, you may use

public Map<String, Set<Item>> filterByStatus(Map<String, Set<Item>> changes) {
    Map<String, Set<Item>> result = new HashMap<>(changes);
    result.replaceAll((key, set) -> {
        set = new HashSet<>(set);
        set.removeIf(item -> Boolean.TRUE.equals(item.status));
        return set;
    });
    // if you want to remove empty sets afterwards:
    result.values().removeIf(Set::isEmpty);
    return result;
}

You could even do the operation in-place if the sets are mutable and you don’t need the old state anymore:

changes.values().forEach(set -> set.removeIf(item -> Boolean.TRUE.equals(item.status)));
// if you want to remove empty sets afterwards (and the map is mutable):
changes.values().removeIf(Set::isEmpty);

you could even remove these items, followed by removing the set only if they became empty due to the removal, in one statement:

changes.values().removeIf(set ->
    set.removeIf(item -> Boolean.TRUE.equals(item.status)) && set.isEmpty());

Upvotes: 2

E. Betanzos
E. Betanzos

Reputation: 1948

This avoid include in new Map entrys with 0 items.

private Map<String,Set<Item>> filterByStatus(Map<String, Set<Item>> changes) {
    return changes.entrySet()
            .stream()
            .filter(entry -> entry.getValue()
                    .stream()
                    .anyMatch(item -> item.status == null || item.status == false))
            .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue()
                    .stream()
                    .filter(item -> item.status == null || item.status == false)
                    .collect(Collectors.toSet()))
            );
}

Upvotes: 0

goat
goat

Reputation: 31813

public Map<String, Set<Item>> filterByStatus(Map<String, Set<Item>> changes) {
    return changes.entrySet()
            .stream()
            .collect(Collectors.toMap(Map.Entry::getKey, entry ->
                entry.getValue()
                    .stream()
                    .filter(item -> item.status == null || item.status == Boolean.FALSE)
                    .collect(Collectors.toSet())
            ));
}

Upvotes: 3

Hugo G
Hugo G

Reputation: 16494

Judging from your description you are looking for allMatch rather than anyMatch.

Currently you get all the sets which contain at least one non-True value. What you seem to want is having only sets that consist of non-True values only.

If you are rather looking for filtering out the negative values from all sets, you should use a mapping, not just filter, on the Map. In the mapping you could create copies of the sets with True values excluded.

Upvotes: 0

Related Questions