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)
}
}
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).
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.
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]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With