web-dev-qa-db-ja.com

Java 8ストリームでデカルト積を作成するにはどうすればよいですか?

次のコレクションタイプがあります。

_Map<String, Collection<String>> map;
_

各キーのコレクション内の単一の値からmap.size()のそれぞれの一意の組み合わせを作成したいと思います。

たとえば、マップが次のようになっているとします。

_A, {a1, a2, a3, ..., an}
B, {b1, b2, b3, ..., bn}
C, {c1, c2, c3, ..., cn}
_

私が取得したい結果は、_List<Set<String>>_の結果に似ています(順序は重要ではありません、すべての可能な組み合わせで構成される「完全な」結果である必要があります):

_{a1, b1, c1},
{a1, b1, c2},
{a1, b1, c3},
{a1, b2, c1},
{a1, b2, c2},
{a1, b2, c3},
...
{a2, b1, c1},
{a2, b1, c2},
...
{a3, b1, c1},
{a3, b1, c2},
...
{an, bn, cn}
_

これは基本的にカウントの問題ですが、Java 8ストリームを使用して解決策が可能かどうかを確認したいと思います。

27
Alex Paransky

これは、再帰flatMapチェーンを使用して解決できます。

まず、マップの値を前後に移動する必要があるので、それらをArrayListにコピーする方が適切です(これはディープコピーではなく、3つの要素のみのArrayListです。そのため、追加のメモリ使用量は低くなります)。

次に、以前にアクセスした要素のプレフィックスを維持するために、ヘルパー不変のPrefixクラスを作成しましょう。

_private static class Prefix<T> {
    final T value;
    final Prefix<T> parent;

    Prefix(Prefix<T> parent, T value) {
        this.parent = parent;
        this.value = value;
    }

    // put the whole prefix into given collection
    <C extends Collection<T>> C addTo(C collection) {
        if (parent != null)
            parent.addTo(collection);
        collection.add(value);
        return collection;
    }
}
_

これは、次のように使用できる非常に単純な不変のリンクリストです。

_List<String> list = new Prefix<>(new Prefix<>(new Prefix<>(null, "a"), "b"), "c")
                          .addTo(new ArrayList<>()); // [a, b, c];
_

次に、flatMapsをチェーンする内部メソッドを作成しましょう。

_private static <T, C extends Collection<T>> Stream<C> comb(
        List<? extends Collection<T>> values, int offset, Prefix<T> prefix,
        Supplier<C> supplier) {
    if (offset == values.size() - 1)
        return values.get(offset).stream()
                     .map(e -> new Prefix<>(prefix, e).addTo(supplier.get()));
    return values.get(offset).stream()
            .flatMap(e -> comb(values, offset + 1, new Prefix<>(prefix, e), supplier));
}
_

再帰のように見えますが、より複雑です。直接呼び出すのではなく、外側のメソッドを呼び出すラムダを渡します。パラメーター:

  • 値:元の値のList(あなたの場合はnew ArrayList<>(map.values))。
  • offset:このリスト内の現在のオフセット
  • prefix:長さオフセットの現在のプレフィックス(または_offset == 0_の場合はnull)。コレクションlist.get(0)list.get(1)からlist.get(offset-1)までのコレクションから現在選択されている要素が含まれています。
  • supplier:結果のコレクションを作成するファクトリメソッド。

値リスト(offset == values.size() - 1)の最後に達したら、サプライヤーを使用して、最後のコレクションの要素を値から最終的な組み合わせにマップします。それ以外の場合は、flatMapを使用します。これは、各中間要素に対してプレフィックスを拡大し、次のオフセットに対してcombメソッドを再度呼び出します。

最後に、この機能を使用するためのパブリックメソッドがあります。

_public static <T, C extends Collection<T>> Stream<C> ofCombinations(
        Collection<? extends Collection<T>> values, Supplier<C> supplier) {
    if (values.isEmpty())
        return Stream.empty();
    return comb(new ArrayList<>(values), 0, null, supplier);
}
_

使用例:

_Map<String, Collection<String>> map = new LinkedHashMap<>(); // to preserve the order
map.put("A", Arrays.asList("a1", "a2", "a3", "a4"));
map.put("B", Arrays.asList("b1", "b2", "b3"));
map.put("C", Arrays.asList("c1", "c2"));

ofCombinations(map.values(), LinkedHashSet::new).forEach(System.out::println);
_

順序を維持するために、個々の組み合わせをLinkedHashSetに再度収集します。代わりに他のコレクションを使用できます(例、_ArrayList::new_)。

15
Tagir Valeev

主にリストを操作するソリューションで、物事がずっと簡単になります。 flatMapで再帰呼び出しを行い、すでに結合されている要素と、まだ欠落している要素のコレクションを追跡し、この入れ子になった再帰構造の結果をリストのストリームとして提供します。

import Java.util.*;
import Java.util.stream.Stream;

public class CartesianProduct {

    public static void main(String[] args) {
        Map<String, Collection<String>> map = 
            new LinkedHashMap<String, Collection<String>>();
        map.put("A", Arrays.asList("a1", "a2", "a3", "a4"));
        map.put("B", Arrays.asList("b1", "b2", "b3"));
        map.put("C", Arrays.asList("c1", "c2"));
        ofCombinations(map.values()).forEach(System.out::println);
    }

    public static <T> Stream<List<T>> ofCombinations(
        Collection<? extends Collection<T>> collections) {
        return ofCombinations(
            new ArrayList<Collection<T>>(collections), 
            Collections.emptyList());        
    }       

    private static <T> Stream<List<T>> ofCombinations(
        List<? extends Collection<T>> collections, List<T> current) {
        return collections.isEmpty() ? Stream.of(current) :
            collections.get(0).stream().flatMap(e -> 
            {
                List<T> list = new ArrayList<T>(current);
                list.add(e);
                return ofCombinations(
                    collections.subList(1, collections.size()), list);
            });
    }
}
10
Marco13

ForEachを使用したJava 8のデカルト積:

List<String> listA = new ArrayList<>();
listA.add("0");
listA.add("1");
List<String> listB = new ArrayList<>();
listB.add("a");
listB.add("b"); 

List<String> cartesianProduct = new ArrayList<>();
listA.forEach(a -> listB.forEach(b -> cartesianProduct.add(a + b)));

cartesianProduct.forEach(System.out::println);
//Output : 0a 0b 1a 1b 
5
Miklos Toth

Tagirの例ほど多くのStreamsの機能を使用しない別のソリューションがあります。しかし、私はそれがより簡単であると信じています:

public class Permutations {

    transient List<Collection<String>> perms;

    public List<Collection<String>> list(Map<String, Collection<String>> map) {

        SortedMap<String, Collection<String>> sortedMap = new TreeMap<>();
        sortedMap.putAll(map);

        sortedMap.values().forEach((v) ->  perms = expand(perms, v));

        return perms;
    }

    private List<Collection<String>> expand(List<Collection<String>> list, Collection<String> elements) {

        List<Collection<String>> newList = new LinkedList<>();

        if (list == null) {
            elements.forEach((e) -> {
                SortedSet<String> set = new TreeSet<>();
                set.add(e);
                newList.add(set);
            });
        } else {
            list.forEach((set) ->
                elements.forEach((e) -> {
                    SortedSet<String> newSet = new TreeSet<>();
                    newSet.addAll(set);
                    newSet.add(e);
                    newList.add(newSet);
                }));
        }

        return newList;
    }
}

要素の順序に関心がない場合は、Sortedプレフィックスを削除できます。ただし、すべてがソートされているとデバッグが容易になると思います。

使用法:

Permutations p = new Permutations();
List<Collection<String>> plist = p.list(map);
plist.forEach((s) -> System.out.println(s));

楽しい!

4
Marin

2つのコレクションの要素のデカルト積を求めるだけの単純な状況に対する、より単純な答え。

flatMapを使用して2つの短いリストのデカルト積を生成するコードを次に示します。

    public static void main(String[] args) {
      List<Integer> aList = Arrays.asList(1,2,3);
      List<Integer> bList = Arrays.asList(4,5,6);

      Stream<List<Integer>> product = aList.stream().flatMap(a -> 
          bList.stream().flatMap(b ->
            Stream.of(Arrays.asList(a, b)))
          );

      product.forEach(p -> { System.out.println(p); });

// prints:
//              [1, 4]
//              [1, 5]
//              [1, 6]
//              [2, 4]
//              [2, 5]
//              [2, 6]
//              [3, 4]
//              [3, 5]
//              [3, 6]
    }

コレクションをさらに追加する場合は、ストリームをさらに1階層ネストします。

        aList.stream().flatMap(a -> 
          bList.stream().flatMap(b ->
            cList.stream().flatMap(c ->
               Stream.of(Arrays.asList(a, b, c))))
          );
1
Jurgen Vinju

ストリームソリューションではありませんが、Guavaのcom.google.common.collect.Setsがそれを行います

Set<List<String>> result = Sets.cartesianProduct(Set.of("a1","a2"), Set.of("b1","b2"), Set.of("c1","c2" ))
1

ループ内で結合リストを作成

List<String> cartesianProduct(List<List<String>> wordLists) {

 List<String> cp = wordLists.get(0);

 for (int i = 1; i < wordLists.size(); i++) 
 {      
     List<String> secondList = wordLists.get(i);
     List<String> combinedList = cp.stream().flatMap(s1 -> secondList.stream().map(s2 -> s1 + s2))
                    .collect(Collectors.toList());
        cp = combinedList;

    }
        return cp;
}
0
ravthiru

Iterableを実装し、現在のアイテムのみをメモリに保持するクラスを作成しました。 Iterable および Iterator は、必要に応じてStreamに変換できます。

class CartesianProduct<T> implements Iterable<List<T>> {
    private final Iterable<? extends Iterable<T>> factors;

    public CartesianProduct(final Iterable<? extends Iterable<T>> factors) {
        this.factors = factors;
    }

    @Override
    public Iterator<List<T>> iterator() {
        return new CartesianProductIterator<>(factors);
    }
}

class CartesianProductIterator<T> implements Iterator<List<T>> {
    private final List<Iterable<T>> factors;
    private final Stack<Iterator<T>> iterators;
    private final Stack<T> current;
    private List<T> next;
    private int index = 0;

    private void computeNext() {
        while (true) {
            if (iterators.get(index).hasNext()) {
                current.add(iterators.get(index).next());
                if (index == factors.size() - 1) {
                    next = new ArrayList<>(current);
                    current.pop();
                    return;
                }
                index++;
                iterators.add(factors.get(index).iterator());
            } else {
                index--;
                if (index < 0) {
                    return;
                }
                iterators.pop();
                current.pop();
            }
        }
    }

    public CartesianProductIterator(final Iterable<? extends Iterable<T>> factors) {
        this.factors = StreamSupport.stream(factors.spliterator(), false)
                .collect(Collectors.toList());
        if (this.factors.size() == 0) {
            index = -1;
        }
        iterators = new Stack<>();
        iterators.add(this.factors.get(0).iterator());
        current = new Stack<>();
        computeNext();
    }

    @Override
    public boolean hasNext() {
        if (next == null && index >= 0) {
            computeNext();
        }
        return next != null;
    }

    @Override
    public List<T> next() {
        if (!hasNext()) {
            throw new IllegalStateException();
        }
        var result = next;
        next = null;
        return result;
    }
}
0
ominug

コンシューマー関数クラス、リスト、およびforeachを使用する

    public void tester(){

        String[] strs1 = {"2","4","9"};
        String[] strs2 = {"9","0","5"};

        //Final output is {"29", "49, 99", "20", "40", "90", "25", "45", "95"}
        List<String> result = new ArrayList<>();
        Consumer<String> consumer = (String str) -> result.addAll(Arrays.stream(strs1).map(s -> s+str).collect(Collectors.toList()));
        Arrays.stream(strs2).forEach(consumer);

        System.out.println(result);

}
0
Olufemi