web-dev-qa-db-ja.com

複数のリストからすべての組み合わせを生成する

それぞれ長さが不明なリストの量が不明である場合、すべての可能な一意の組み合わせで特異なリストを生成する必要があります。たとえば、次のリストがあるとします。

X: [A, B, C] 
Y: [W, X, Y, Z]

次に、12の組み合わせを生成できるはずです。

[AW, AX, AY, AZ, BW, BX, BY, BZ, CW, CX, CY, CZ]

3つの要素の3番目のリストが追加された場合、36個の組み合わせがあります。

Javaでこれをどのように行うことができますか?
(擬似コードでも問題ありません)

41
Michael Hillman

再帰が必要です:

すべてのリストがlists(リストのリスト)にあるとしましょう。 resultを必要な順列のリストにします。次のように実装できます。

void generatePermutations(List<List<Character>> lists, List<String> result, int depth, String current) {
    if (depth == lists.size()) {
        result.add(current);
        return;
    }

    for (int i = 0; i < lists.get(depth).size(); i++) {
        generatePermutations(lists, result, depth + 1, current + lists.get(depth).get(i));
    }
}

最終的な呼び出しは次のようになります。

generatePermutations(lists, result, 0, "");
56
Armen Tsirunyan

このトピックは役に立ちました。以前のソリューションをJavaおよびよりユーザーフレンドリーに完全に書き直しました。さらに、柔軟性を高めるためにコレクションとジェネリックを使用します。

/**
 * Combines several collections of elements and create permutations of all of them, taking one element from each
 * collection, and keeping the same order in resultant lists as the one in original list of collections.
 * 
 * <ul>Example
 * <li>Input  = { {a,b,c} , {1,2,3,4} }</li>
 * <li>Output = { {a,1} , {a,2} , {a,3} , {a,4} , {b,1} , {b,2} , {b,3} , {b,4} , {c,1} , {c,2} , {c,3} , {c,4} }</li>
 * </ul>
 * 
 * @param collections Original list of collections which elements have to be combined.
 * @return Resultant collection of lists with all permutations of original list.
 */
public static <T> Collection<List<T>> permutations(List<Collection<T>> collections) {
  if (collections == null || collections.isEmpty()) {
    return Collections.emptyList();
  } else {
    Collection<List<T>> res = Lists.newLinkedList();
    permutationsImpl(collections, res, 0, new LinkedList<T>());
    return res;
  }
}

/** Recursive implementation for {@link #permutations(List, Collection)} */
private static <T> void permutationsImpl(List<Collection<T>> ori, Collection<List<T>> res, int d, List<T> current) {
  // if depth equals number of original collections, final reached, add and return
  if (d == ori.size()) {
    res.add(current);
    return;
  }

  // iterate from current collection and copy 'current' element N times, one for each element
  Collection<T> currentCollection = ori.get(d);
  for (T element : currentCollection) {
    List<T> copy = Lists.newLinkedList(current);
    copy.add(element);
    permutationsImpl(ori, res, d + 1, copy);
  }
}

コレクションの作成にグアバライブラリを使用しています。

16
Víctor

この操作は デカルト積 と呼ばれます。グアバはそのためのユーティリティ関数を提供します: Lists.cartesianProduct

7
Pedro Oliveira

再帰なし一意の組み合わせ:

    String sArray[] = new String []{"A", "A", "B", "C"};
    //convert array to list
    List<String> list1 = Arrays.asList(sArray);
    List<String> list2 = Arrays.asList(sArray);
    List<String> list3 = Arrays.asList(sArray);

    LinkedList<List <String>> lists = new LinkedList<List <String>>();

    lists.add(list1);
    lists.add(list2);
    lists.add(list3);

    Set<String> combinations = new TreeSet<String>();
    Set<String> newCombinations;

    for (String s: lists.removeFirst())
        combinations.add(s);

    while (!lists.isEmpty()) {
        List<String> next = lists.removeFirst();
        newCombinations =  new TreeSet<String>();
        for (String s1: combinations) 
            for (String s2 : next) 
              newCombinations.add(s1 + s2);               

        combinations = newCombinations;
    }
    for (String s: combinations)
        System.out.print(s+" ");    
5

ここで他の回答で提供されているネストされたループソリューションを使用して、2つのリストを結合します。

3つ以上のリストがある場合、

  1. 最初の2つを1つの新しいリストに結合します。
  2. 結果のリストを次の入力リストと組み合わせます。
  3. 繰り返す。
3
mbeckish

いつものようにパーティーに遅れていますが、ここでは配列を使用してうまく説明した例を示します。これはリストに合わせて簡単に変更できます。私のユースケースでは、辞書式の順序で複数の配列のすべてのユニークな組み合わせが必要でした。

ここに答えが明確なアルゴリズムを与えていないので、私はそれを投稿しました、そして、私は再帰に耐えられません。結局のところstackoverflowではないのでしょうか?

String[][] combinations = new String[][] {
                 new String[] { "0", "1" },
                 new String[] { "0", "1" },
                 new String[] { "0", "1" },
                 new String[] { "0", "1" } };

    int[] indices = new int[combinations.length];

    int currentIndex = indices.length - 1;
    outerProcess: while (true) {

        for (int i = 0; i < combinations.length; i++) {
            System.out.print(combinations[i][indices[i]] + ", ");
        }
        System.out.println();

        while (true) {
            // Increase current index
            indices[currentIndex]++;
            // If index too big, set itself and everything right of it to 0 and move left
            if (indices[currentIndex] >= combinations[currentIndex].length) {
                for (int j = currentIndex; j < indices.length; j++) {
                    indices[j] = 0;
                }
                currentIndex--;
            } else {
                // If index is allowed, move as far right as possible and process next
                // combination
                while (currentIndex < indices.length - 1) {
                    currentIndex++;
                }
                break;
            }
            // If we cannot move left anymore, we're finished
            if (currentIndex == -1) {
                break outerProcess;
            }
        }
    }

出力;

0000

0001

0010

0011

0100

0101

0110

0111

1000

1001

1010

1011

1100

1101

1110

1111

2
professorcolm
  • 再帰なし
  • 注文されています
  • インデックスによって特定の組み合わせを取得することが可能です(他のすべての順列を構築せずに):

最後にクラスとmain()メソッド:

public class TwoDimensionalCounter<T> {
    private final List<List<T>> elements;

    public TwoDimensionalCounter(List<List<T>> elements) {
        this.elements = Collections.unmodifiableList(elements);
    }

    public List<T> get(int index) {
        List<T> result = new ArrayList<>();
        for(int i = elements.size() - 1; i >= 0; i--) {
            List<T> counter = elements.get(i);
            int counterSize = counter.size();
            result.add(counter.get(index % counterSize));
            index /= counterSize;
        }
        return result;//Collections.reverse() if you need the original order
    }

    public int size() {
        int result = 1;
        for(List<T> next: elements) result *= next.size();
        return result;
    }

    public static void main(String[] args) {
        TwoDimensionalCounter<Integer> counter = new TwoDimensionalCounter<>(
                Arrays.asList(
                        Arrays.asList(1, 2, 3),
                        Arrays.asList(1, 2, 3),
                        Arrays.asList(1, 2, 3)
                ));
        for(int i = 0; i < counter.size(); i++)
            System.out.println(counter.get(i));
    }
}

実装する必要がある操作は、デカルト積と呼ばれます。詳細については、 https://en.wikipedia.org/wiki/Cartesian_product を参照してください

必要なことを正確に行えるオープンソースライブラリを使用することをお勧めします。 https://github.com/SurpSG/Kombi

使用方法の例があります: https://github.com/SurpSG/Kombi#usage-for-lists-1

:ライブラリは、高性能目的のために設計されました。ベンチマークの結果を確認できます こちら

ライブラリは、かなり良いスループットと一定のメモリ使用量を提供します

1
SergiiGnatiuk