期望从输入列表itemIsBoth
派生3个列表aItems
,bItems
,items
。
如何将如下代码转换为功能样式? (我知道这段代码在命令式样式中足够清晰,但是我想知道声明式样式确实不能处理这样一个简单的示例)。谢谢。
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)
}
}
答案 0 :(得分:4)
问题标题相当广泛(转换if-else阶梯),但是由于实际问题询问的是特定情况,因此让我提供一个示例,至少可以说明可以做什么。
由于if-else
结构基于应用于该项目的谓词创建了三个不同的列表,因此我们可以将这种行为声明为一个分组操作。开箱即用的工作唯一需要做的就是使用标记对象折叠多个布尔谓词。例如:
class Item {
enum Category {A, B, AB}
public Category getCategory() {
return /* ... */;
}
}
那么逻辑可以简单地表达为:
Map<Item.Category, List<Item>> categorized =
items.stream().collect(Collectors.groupingBy(Item::getCategory));
在给定类别的情况下可以从地图中检索每个列表。
如果无法更改类Item
,则可以通过移动枚举声明和大于Item
类的分类方法来实现相同的效果(该方法将成为静态方法)。 / p>
答案 1 :(得分:4)
另一种使用Vavr且仅对项目列表进行一次迭代的解决方案可以使用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)))
))
);
它将返回一个包含三个结果列表的元组。
答案 2 :(得分:1)
当然可以。功能性方法是使用声明性方法。
从数学上讲,您正在设置Equivalence relation,然后您就可以编写
Map<String, List<Item>> ys = xs
.stream()
.collect(groupingBy(x -> here your equivalence relation))
一个简单的例子展示
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);
}
}
有输出
true,true=[com.foo.Main$Item@64616ca2, com.foo.Main$Item@13fee20c]
false,true=[com.foo.Main$Item@4e04a765]
答案 3 :(得分:1)
由于您已经提到了vavr作为标签,所以我将提供使用vavr集合的解决方案。
import static io.vavr.Predicates.allOf;
import static io.vavr.Predicates.not;
...
final Array<Item> itemIsBoth = items.filter(allOf(Item::isA, Item::isB));
final Array<Item> aItems = items.filter(allOf(Item::isA, not(Item::isB)));
final Array<Item> bItems = items.filter(allOf(Item::isB, not(Item::isA)));
此解决方案的优点是一目了然,易于理解,并且具有Java所具有的功能。缺点是它将对原始集合进行三次迭代,而不是一次。仍然是一个 O(n),但是乘数常数为3。在非关键代码路径上且集合很小的情况下,为了代码清晰起见,可能需要花费几个CPU周期。 >
当然,这也适用于所有其他vavr集合,因此您可以将Array
替换为List
,Vector
,Stream
等。
答案 4 :(得分:0)
不是(在某种意义上是使用函数的)使用lambda左右的,而是在仅使用函数(按照数学方法)并且在任何地方都没有局部状态/变量的意义上是相当有用的:
/* returns 0, 1, 2 or 3 according to isA/isB */
int getCategory(Item item) {
return item.isA() ? 1 : 0 + 2 * (item.isB() ? 1 : 0)
}
LinkedList<Item>[] lists = new LinkedList<Item> { initializer for 4-element array here };
{
for (Item item: items) {
lists[getCategory(item)].addLast(item);
}
}
答案 5 :(得分:0)
这个问题似乎有些争议(在撰写本文时为+ 5 / -3)。
正如您所提到的,这里的命令式解决方案很可能是最简单,适当和易读的解决方案。
功能性或声明性样式并没有真正“失败”。而是在提出关于确切目标,条件和上下文的问题,甚至是关于语言细节的哲学问题(例如,为什么核心Java中没有标准的Pair
类)。
您可以在此处应用功能解决方案。一个简单的技术问题是您是否真的要填充现有列表,或者是否可以创建新列表。在这两种情况下,都可以使用Collectors#groupingBy
方法。
在两种情况下,分组标准都是相同的:即,一项的isA
和isB
的特定组合的任何“表示”。有不同的解决方案。在下面的示例中,我使用了Entry<Boolean, Boolean>
作为密钥。
(如果您还有其他条件,例如isC
和isD
,那么实际上您也可以使用List<Boolean>
)。
该示例显示了如何将项目添加到现有列表(如您的问题),或创建新列表(一点点更简单,更简洁)。
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public class FunctionalIfElse
{
public static void main(String[] args)
{
List<Item> items = new ArrayList<Item>();
items.add(new Item(false, false));
items.add(new Item(false, true));
items.add(new Item(true, false));
items.add(new Item(true, true));
fillExistingLists(items);
createNewLists(items);
}
private static void fillExistingLists(List<Item> items)
{
System.out.println("Filling existing lists:");
List<Item> itemIsBoth = new ArrayList<Item>();
List<Item> aItems = new ArrayList<Item>();
List<Item> bItems = new ArrayList<Item>();
Map<Entry<Boolean, Boolean>, List<Item>> map =
new LinkedHashMap<Entry<Boolean, Boolean>, List<Item>>();
map.put(entryWith(true, true), itemIsBoth);
map.put(entryWith(true, false), aItems);
map.put(entryWith(false, true), bItems);
items.stream().collect(Collectors.groupingBy(
item -> entryWith(item.isA(), item.isB()),
() -> map, Collectors.toList()));
System.out.println("Both");
itemIsBoth.forEach(System.out::println);
System.out.println("A");
aItems.forEach(System.out::println);
System.out.println("B");
bItems.forEach(System.out::println);
}
private static void createNewLists(List<Item> items)
{
System.out.println("Creating new lists:");
Map<Entry<Boolean, Boolean>, List<Item>> map =
items.stream().collect(Collectors.groupingBy(
item -> entryWith(item.isA(), item.isB()),
LinkedHashMap::new, Collectors.toList()));
List<Item> itemIsBoth = map.get(entryWith(true, true));
List<Item> aItems = map.get(entryWith(true, false));
List<Item> bItems = map.get(entryWith(false, true));
System.out.println("Both");
itemIsBoth.forEach(System.out::println);
System.out.println("A");
aItems.forEach(System.out::println);
System.out.println("B");
bItems.forEach(System.out::println);
}
private static <K, V> Entry<K, V> entryWith(K k, V v)
{
return new SimpleEntry<K, V>(k, v);
}
static class Item
{
private boolean a;
private boolean b;
public Item(boolean a, boolean b)
{
this.a = a;
this.b = b;
}
public boolean isA()
{
return a;
}
public boolean isB()
{
return b;
}
@Override
public String toString()
{
return "(" + a + ", " + b + ")";
}
}
}
答案 6 :(得分:0)
摆脱if-else
的另一种方法是将它们替换为Predicate
和Consumer
:
Map<Predicate<Item>, Consumer<Item>> actions =
Map.of(item.predicateA(), aItems::add, item.predicateB(), bItems::add);
actions.forEach((key, value) -> items.stream().filter(key).forEach(value));
因此,您需要使用在Item
和predicateA()
中实现的逻辑,同时用predicateB()
和isA()
来增强isB()
>
顺便说一句,我仍然建议您使用if-else
逻辑。