簡體   English   中英

如何從一組對象生成組合?

[英]How to generate combinations from a set of objects?

我需要從一組 7 個對象中獲取5 個對象的所有可能組合 不重復的組合(選擇的順序無關緊要,即以不同的順序選擇的相同對象被視為相同的組合)。

我有實現,它工作正常並產生正確的結果:

String[] vegetablesSet = {"Pepper", "Cabbage", "Tomato", "Carrot", "Beans", "Cucumber", "Peas"};
final int SALAD_COMBINATION_SIZE = 5; // Example: {"Tomato", "Cabbage", "Cucumber", "Pepper", "Carrot"} 

Set<Set<String>> allSaladCombinations = new HashSet<>();
for (int i = 1, max = 1 << vegetablesSet.length; i < max; i++) {
   Set<String> set = new HashSet<>();
   int count = 0;
   for (int j = 0, k = 1; j < vegetablesSet.length; j++, k <<= 1) {
      if ((k & i) != 0) {
         set.add(vegetablesSet[j]);
         count++;
      }
   }
   if (count == SALAD_COMBINATION_SIZE) {
      allSaladCombinations.add(set);
   }
}

for (Set<String> set : allSaladCombinations) {
   for (String vegatable : set) {
      System.out.print(vegatable + " ");
   }
   System.out.println();
}

輸出正確:已找到 21 個正確的組合。

但它使用按位運算符,在我的評估中,它的可讀性、可維護性和可擴展性不是很好。 我想重構或完全重寫它,使其成為一種更靈活、更易於理解的面向對象方法。 我對如何使用 OOP 和 recursion做到這一點非常感興趣。

我不在我的項目中使用Google GuavaApache CommonsCombinatoricsLib 而且我不想只為一種方法包含整個第三方庫。 我在網站上搜索類似的問題,但只找到了很好的清晰排列實現: https : //stackoverflow.com/a/14486955

這些情況有一些相似的含義,但對象的順序對我來說並不重要,在我的情況下,它們被認為是相同的組合,不應計算。

你可以試試這個代碼:

    public static void main(String[] args) {
        String[] vegetablesSet = { "Pepper", "Cabbage", "Tomato", "Carrot", "Beans", "Cucumber", "Peas" };
        Set<Set<String>> result = new HashSet<>();      
        combinations(vegetablesSet, new ArrayList<>(), result, 5, 0);
        result.forEach(System.out::println);
    }

    public static void combinations(String[] values, List<String> current, Set<Set<String>> accumulator, int size, int pos) {
        if (current.size() == size) {
            Set<String> toAdd = current.stream().collect(Collectors.toSet());
            if (accumulator.contains(toAdd)) {
                throw new RuntimeException("Duplicated value " + current);
            }
            accumulator.add(toAdd);
            return;
        }
        for (int i = pos; i <= values.length - size + current.size(); i++) {
            current.add(values[i]);
            combinations(values, current, accumulator, size, i + 1);
            current.remove(current.size() - 1);
        }
    }

基本思想是僅從當前位置開始使用元素並使用遞歸來混合不同的選項。

如果你想要一個更簡單的方法調用,你可以像這樣創建一個包裝方法:

    public static Set<Set<String>> combinations(String[] values) {
        Set<Set<String>> result = new HashSet<>();
        combinations(values, new ArrayList<>(), result, SALAD_COMBINATION_SIZE, 0);
        return result;
    }

編輯:另一種方法是使 1 和 0(在這種情況下為 5 個 1 和 2 個零)的不同排列來創建掩碼,然后將在組合中進行轉換。 我覺得它比之前的解決方案更自然,因為它不使用循環,除了掩碼應用程序(隱含在流操作中):

    public static void combinations2(String[] values, String current, Set<Set<String>> accumulator, int ones, int zeroes) {
        if (ones + zeroes == 0) {
            accumulator.add(IntStream.range(0, values.length)
                    .filter(position -> '1' == current.charAt(position))
                    .mapToObj(position -> values[position])
                    .collect(Collectors.toSet()));
            return;
        }
        if (ones > 0) {
            combinations2(values, current + "1", accumulator, ones - 1, zeroes);
        }
        if (zeroes > 0) {
            combinations2(values, current + "0", accumulator, ones, zeroes - 1);
        }
    }

包裝方法:

public static Set<Set<String>> combinations(String[] values) {
        Set<Set<String>> result = new HashSet<>();
        combinations2(values, "", result, SALAD_COMBINATION_SIZE, values.length - SALAD_COMBINATION_SIZE);
        return result;
    }

暫無
暫無

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

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