Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Functional Programming: How to convert a if-else ladder inside for loop to functional style?

The expectation is derive 3 lists itemIsBoth, aItems, bItems from the input list items. How to convert code like below to functional style? (I understand this code is clear enough in an imperative style, but I want to know does declarative style really fail to deal with such a simple example). Thanks.

for (Item item: items) {
    if (item.isA() && item.isB()) {
        itemIsBoth.add(item);
    } else if (item.isA()) {
        aItems.add(item);
    } else if (item.isB()){
        bItems.add(item)
    }
}
like image 657
Gopal S Akshintala Avatar asked Jul 18 '19 13:07

Gopal S Akshintala


3 Answers

The question title is quite broad (convert if-else ladder), but since the actual question asks about a specific scenario, let me offer a sample that can at least illustrate what can be done.

Because the if-else structure creates three distinct lists based on a predicate applied to the item, we can express this behavior more declaratively as a grouping operation. The only extra needed to make this work out of the box would be to collapse the multiple Boolean predicates using a tagging object. For example:

class Item {
    enum Category {A, B, AB}

    public Category getCategory() {
        return /* ... */;
    }
}

Then the logic can be expressed simply as:

Map<Item.Category, List<Item>> categorized = 
    items.stream().collect(Collectors.groupingBy(Item::getCategory));

where each list can be retrieved from the map given its category.

If it's not possible to change class Item, the same effect can be achieved by moving the enum declaration and the categorization method outsize the Item class (the method would become a static method).

like image 185
SDJ Avatar answered Oct 23 '22 06:10

SDJ


Another solution using Vavr and doing only one iteration over a list of items might be achieved using foldLeft:

list.foldLeft(
    Tuple.of(List.empty(), List.empty(), List.empty()), //we declare 3 lists for results
    (lists, item) -> Match(item).of(
        //both predicates pass, add to first list
        Case($(allOf(Item::isA, Item::isB)), lists.map1(l -> l.append(item))),
        //is a, add to second list
        Case($(Item::isA), lists.map2(l -> l.append(item))),
        //is b, add to third list
        Case($(Item::isB), lists.map3(l -> l.append(item)))
    ))
);

It will return a tuple containing three lists with results.

like image 43
Krzysztof Atłasik Avatar answered Oct 23 '22 06:10

Krzysztof Atłasik


Of course, you can. The functional way is to use declarative ways.

Mathematically you are setting an Equivalence relation, then, you can write

Map<String, List<Item>> ys = xs
    .stream()
    .collect(groupingBy(x -> here your equivalence relation))

A simple example show this

public class Main {

    static class Item {
        private final boolean a;
        private final boolean b;

        Item(boolean a, boolean b) {
            this.a = a;
            this.b = b;
        }

        public boolean isB() {
            return b;
        }

        public boolean isA() {
            return a;
        }
    }

    public static void main(String[] args) {
        List<Item> xs = asList(new Item(true, true), new Item(true, true), new Item(false, true));
        Map<String, List<Item>> ys = xs.stream().collect(groupingBy(x -> x.isA() + "," + x.isB()));
        ys.entrySet().forEach(System.out::println);
    }
}

With output

true,true=[com.foo.Main$Item@64616ca2, com.foo.Main$Item@13fee20c]
false,true=[com.foo.Main$Item@4e04a765]
like image 1
josejuan Avatar answered Oct 23 '22 06:10

josejuan