簡體   English   中英

Java函數編程:如何將for循環中的if-else梯形圖轉換為函數式?

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

期望是從輸入列表items導出3列出itemIsBothaItemsbItems 如何將下面的代碼轉換為功能樣式? (我理解這個代碼在命令式樣式中足夠清楚,但我想知道聲明式樣式真的無法處理這么簡單的例子)。 謝謝。

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)
    }
}

問題標題相當廣泛(轉換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類(該方法將成為靜態方法)來實現相同的效果。

使用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)))
    ))
);

它將返回一個包含三個帶有結果的列表的元組。

當然可以。 功能方式是使用聲明方式。

在數學上你正在設置一個等價關系 ,然后,你可以寫

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]

既然你已經提到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集合,因此您可以使用ListVectorStream等替換Array

你可以擺脫if-else另一種方法是用PredicateConsumer替換它們:

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));

因此,你需要enhace你的Item有兩個mehods predicateA()predicateB()使用您已經於您實現的邏輯isA()isB()

順便說一下,我仍然建議使用你的if-else邏輯。

不是(在某種意義上的功能)使用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 / -3)。

正如您所提到的,這里的命令式解決方案很可能是最簡單,最合適和最易讀的解決方案。

功能或聲明風格並沒有真正“失敗”。 它反而提出了關於確切目標,條件和背景的問題,甚至可能是關於語言細節的哲學問題(比如為什么核心Java中沒有標准的Pair類)。

可以在此處應用功能解決方案。 一個簡單的技術問題是,您是否真的想要填充現有列表,或者是否可以創建新列表。 在這兩種情況下,您都可以使用Collectors#groupingBy方法。

的分組標准是在兩種情況下是相同的:即,具體組合的任何“表示” isAisB一個項目。 對此有不同的可能解決方案。 在下面的示例中,我使用Entry<Boolean, Boolean>作為鍵。

(如果您有其他條件,如isCisD ,那么你可以在事實上還使用了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 + ")";
        }
    }

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM