web-dev-qa-db-ja.com

リストの要素をサブリストにグループ化する(おそらくグアバを使用して)

リストの要素をグループ化したい。私は現在この方法でやっています:

public static <E> List<List<E>> group(final List<E> list, final GroupFunction<E> groupFunction) {

    List<List<E>> result = Lists.newArrayList();

    for (final E element : list) {

        boolean groupFound = false;
        for (final List<E> group : result) {
            if (groupFunction.sameGroup(element, group.get(0))) {
                group.add(element);
                groupFound = true;
                break;
            }
        }
        if (! groupFound) {

            List<E> newGroup = Lists.newArrayList();
            newGroup.add(element);
            result.add(newGroup);
        }
    }

    return result;
}

public interface GroupFunction<E> {
    public boolean sameGroup(final E element1, final E element2);
}

できればグアバを使用して、これを行うより良い方法はありますか?

36
Fabian Zeindl

確かにそれは可能ですが、グアバではさらに簡単です:) Multimaps.index(Iterable, Function)

_ImmutableListMultimap<E, E> indexed = Multimaps.index(list, groupFunction);
_

具体的なユースケースを指定すると、実際に使用するのが簡単になります。

ドキュメントの例:

_List<String> badGuys =
   Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
Function<String, Integer> stringLengthFunction = ...;
Multimap<Integer, String> index =
   Multimaps.index(badGuys, stringLengthFunction);
System.out.println(index);
_

プリント

_{4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}
_

あなたの場合、GroupFunctionが次のように定義されている場合:

_GroupFunction<String> groupFunction = new GroupFunction<String>() {
  @Override public String sameGroup(final String s1, final String s2) {
    return s1.length().equals(s2.length());
  }
}
_

次のように変換されます:

_Function<String, Integer> stringLengthFunction = new Function<String, Integer>() {
  @Override public Integer apply(final String s) {
    return s.length();
  }
}
_

guavaの例で使用されているstringLengthFunction実装が可能です。


最後に、Java 8で、ランバとメソッドの参照がインライン化するのに十分簡潔であるため、スニペット全体がさらに単純になる可能性があります。

_ImmutableListMultimap<E, E> indexed = Multimaps.index(list, String::length);
_

純粋なJava 8(グアバなし)の例 _Collector.groupingBy_ を使用) Jeffrey Bosboom's answer を参照してください。ただし、違いはほとんどありませんアプローチ:

  • ImmutableListMultimapではなく、Mapの値を持つCollectionを返します。
  • 返されるマップのタイプ、可変性、直列化可能性、スレッドセーフ性についての保証はありませんsource )、

  • guava +メソッドのリファレンスよりも少し冗長です。

[〜#〜] edit [〜#〜]:インデックスキーを気にしない場合は、グループ化された値を取得できます:

_List<List<E>> grouped = Lists.transform(indexed.keySet().asList(), new Function<E, List<E>>() {
        @Override public List<E> apply(E key) {
            return indexed.get(key);
        }
});

// or the same view, but with Java 8 lambdas:
List<List<E>> grouped = Lists.transform(indexed.keySet().asList(), indexed::get);
_

最初に望んでいたように、どのコンテンツをArrayListに簡単にコピーできるか、またはそのまま使用できるかを_Lists<List<E>>_ビューに表示します。また、indexed.get(key)ImmutableListであることに注意してください。

_// bonus: similar as above, but not a view, instead collecting to list using streams:
List<List<E>> grouped = indexed.keySet().stream()
    .map(indexed::get)
    .collect(Collectors.toList());
_

EDIT 2:Petr Gladkikhが 下のコメント に言及しているように、_Collection<List<E>>_で十分な場合、上記の例はより簡単になります:

_Collection<List<E>> grouped = indexed.asMap().values();
_
71
Xaerxess

Collector.groupingBy from Java 8ストリームライブラリは、グアバのMultimaps.indexXaerxessの答え の例を次に示します。Java 8ストリームを使用するように書き直されました。

List<String> badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
Map<Integer, List<String>> index = badGuys.stream()
    .collect(Collectors.groupingBy(String::length));
System.out.println(index);

これは印刷されます

{4=[Inky], 5=[Pinky, Pinky, Clyde], 6=[Blinky]}

リストを作成する以外の方法で値を同じキーと組み合わせたい場合は、別のコレクターを取得するgroupingByのオーバーロードを使用できます。この例では、文字列を区切り文字で連結します。

Map<Integer, String> index = badGuys.stream()
    .collect(Collectors.groupingBy(String::length, Collectors.joining(" and ")));

これは印刷されます

{4=Inky, 5=Pinky and Pinky and Clyde, 6=Blinky}

リストが大きい場合、またはグループ化関数が高価な場合は、parallelStreamと並行コレクターを使用して並列に実行できます。

Map<Integer, List<String>> index = badGuys.parallelStream()
    .collect(Collectors.groupingByConcurrent(String::length));

これは印刷される場合があります(順序はもはや決定的ではありません)

{4=[Inky], 5=[Pinky, Clyde, Pinky], 6=[Blinky]}
10
Jeffrey Bosboom

最も簡単で簡単な方法は、次を使用することです: Lamdaj grouping feature

上記の例を書き換えることができます。

List<String> badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
Group group = group(badGuys, by(on(String.class).length)));
System.out.println(group.keySet());
4
David Zhao

Java 8、グアバといくつかのヘルパー関数を使用すると、カスタムコンパレータでグループ化を実装できます

public static <T> Map<T, List<T>> group(List<T> items, Comparator<T> comparator)
{
    ListMultimap<T, T> blocks = LinkedListMultimap.create();

    if (!ArrayUtils.isNullOrEmpty(items))
    {
        T currentItem = null;

        for (T item : items)
        {
            if (currentItem == null || comparator.compare(currentItem, item) != 0)
            {
                currentItem = item;
            }

            blocks.put(currentItem, ObjectUtils.clone(item));
        }
    }

    return Multimaps.asMap(blocks);
}

Comparator<SportExercise> comparator = Comparator.comparingInt(SportExercise::getEstimatedTime)
                .thenComparingInt(SportExercise::getActiveTime).thenComparingInt(SportExercise::getIntervalCount)
                .thenComparingLong(SportExercise::getExerciseId);

Map<SportExercise, List<SportExercise>> blocks = group(sportWorkout.getTrainingExercises(), comparator);

blocks.forEach((key, values) -> {
            System.out.println(key);
            System.out.println(values);
        });
1
kayz1