Java 8ラムダとストリーム)でmax
関数を少しテストしましたが、max
が実行された場合、複数の場合でもオブジェクトは0と比較され、同格の候補内の任意の要素をさらに考慮せずに返します。
すべての最大値が返されるように、このような予想される最大の動作に明らかなトリックまたは機能はありますか? APIには何も表示されませんが、手動で比較するよりも優れたものである必要があります。
例えば:
// myComparator is an IntegerComparator
Stream.of(1, 3, 5, 3, 2, 3, 5)
.max(myComparator)
.forEach(System.out::println);
// Would print 5, 5 in any order.
OPはコンパレーターを使用して入力を等価クラスに分割していると思います。望ましい結果は、そのコンパレーターに従って最大である等価クラスのメンバーのリストです。
残念ながら、サンプル問題としてint
値を使用するのはひどい例です。すべての等しいint
値は交換可能であるため、同等の値の順序を維持するという概念はありません。おそらくより良い例は、文字列の長さを使用することです。この場合、望ましい結果は、入力からすべてが最も長い入力から文字列のリストを返すことです。
コレクションに少なくとも部分的な結果を保存せずにこれを行う方法は知りません。
入力コレクションを考えると、
List<String> list = ... ;
これは2つのパスでこれを実行するのに十分簡単です。最初のパスは最長の長さを取得し、2番目のパスはその長さの文字列をフィルタリングします。
int longest = list.stream()
.mapToInt(String::length)
.max()
.orElse(-1);
List<String> result = list.stream()
.filter(s -> s.length() == longest)
.collect(toList());
入力が 2回以上トラバースできない のストリームである場合、コレクターを使用して単一のパスのみで結果を計算することが可能です。このようなコレクターを作成することは難しくありませんが、処理する必要のあるいくつかのケースがあるため、少し面倒です。コンパレーターを指定して、このようなコレクターを生成するヘルパー関数は次のとおりです。
static <T> Collector<T,?,List<T>> maxList(Comparator<? super T> comp) {
return Collector.of(
ArrayList::new,
(list, t) -> {
int c;
if (list.isEmpty() || (c = comp.compare(t, list.get(0))) == 0) {
list.add(t);
} else if (c > 0) {
list.clear();
list.add(t);
}
},
(list1, list2) -> {
if (list1.isEmpty()) {
return list2;
}
if (list2.isEmpty()) {
return list1;
}
int r = comp.compare(list1.get(0), list2.get(0));
if (r < 0) {
return list2;
} else if (r > 0) {
return list1;
} else {
list1.addAll(list2);
return list1;
}
});
}
これは、中間結果をArrayList
に格納します。不変条件は、そのようなリスト内のすべての要素は、コンパレータに関して同等であるということです。要素を追加するときに、リストの要素より少ない場合は無視されます。等しい場合は追加されます。それより大きい場合、リストは空になり、新しい要素が追加されます。マージもそれほど難しくありません。要素が大きい方のリストが返されますが、要素が等しい場合はリストが追加されます。
入力ストリームを考えると、これは非常に使いやすいです。
Stream<String> input = ... ;
List<String> result = input.collect(maxList(comparing(String::length)));
カスタムダウンストリームコレクターを使用して、より一般的なコレクターソリューションを実装しました。おそらく一部の読者はそれを便利だと思うかもしれません:
public static <T, A, D> Collector<T, ?, D> maxAll(Comparator<? super T> comparator,
Collector<? super T, A, D> downstream) {
Supplier<A> downstreamSupplier = downstream.supplier();
BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
BinaryOperator<A> downstreamCombiner = downstream.combiner();
class Container {
A acc;
T obj;
boolean hasAny;
Container(A acc) {
this.acc = acc;
}
}
Supplier<Container> supplier = () -> new Container(downstreamSupplier.get());
BiConsumer<Container, T> accumulator = (acc, t) -> {
if(!acc.hasAny) {
downstreamAccumulator.accept(acc.acc, t);
acc.obj = t;
acc.hasAny = true;
} else {
int cmp = comparator.compare(t, acc.obj);
if (cmp > 0) {
acc.acc = downstreamSupplier.get();
acc.obj = t;
}
if (cmp >= 0)
downstreamAccumulator.accept(acc.acc, t);
}
};
BinaryOperator<Container> combiner = (acc1, acc2) -> {
if (!acc2.hasAny) {
return acc1;
}
if (!acc1.hasAny) {
return acc2;
}
int cmp = comparator.compare(acc1.obj, acc2.obj);
if (cmp > 0) {
return acc1;
}
if (cmp < 0) {
return acc2;
}
acc1.acc = downstreamCombiner.apply(acc1.acc, acc2.acc);
return acc1;
};
Function<Container, D> finisher = acc -> downstream.finisher().apply(acc.acc);
return Collector.of(supplier, accumulator, combiner, finisher);
}
したがって、デフォルトでリストに収集できます。
public static <T> Collector<T, ?, List<T>> maxAll(Comparator<? super T> comparator) {
return maxAll(comparator, Collectors.toList());
}
ただし、他のダウンストリームコレクターも使用できます。
public static String joinLongestStrings(Collection<String> input) {
return input.stream().collect(
maxAll(Comparator.comparingInt(String::length), Collectors.joining(","))));
}
値でグループ化し、値をTreeMap
に格納して値を並べ替え、最後のエントリを次のように取得して最大値を取得します。
Stream.of(1, 3, 5, 3, 2, 3, 5)
.collect(groupingBy(Function.identity(), TreeMap::new, toList()))
.lastEntry()
.getValue()
.forEach(System.out::println);
出力:
5
5
私がよく理解していれば、ストリーム内のmax
値の頻度が必要です。
これを実現する1つの方法は、ストリームから要素を収集するときに結果を_TreeMap<Integer, List<Integer>
_に格納することです。次に、最後のキー(または最初に指定したコンパレーターに応じて)を取得して、最大値のリストを含む値を取得します。
_List<Integer> maxValues = st.collect(toMap(i -> i,
Arrays::asList,
(l1, l2) -> Stream.concat(l1.stream(), l2.stream()).collect(toList()),
TreeMap::new))
.lastEntry()
.getValue();
_
Stream(4, 5, -2, 5, 5)
から収集すると、_List [5, 5, 5]
_が得られます。
同じ趣旨のもう1つのアプローチは、counting()
コレクターと組み合わせたgroup by操作を使用することです。
_Entry<Integer, Long> maxValues = st.collect(groupingBy(i -> i,
TreeMap::new,
counting())).lastEntry(); //5=3 -> 5 appears 3 times
_
基本的に、最初に_Map<Integer, List<Integer>>
_を取得します。次に、ダウンストリームのcounting()
コレクターは、キーによってマップされた各リスト内の要素の数を返し、結果としてマップが生成されます。そこから最大エントリを取得します。
最初のアプローチでは、ストリームからすべての要素を格納する必要があります。中間のList
はビルドされないため、2番目の方法の方が優れています(Holgerのコメントを参照)。どちらのアプローチでも、結果は1回のパスで計算されます。
コレクションからソースを取得する場合は、_Collections.max
_を1回使用して最大値を検索し、次に_Collections.frequency
_を使用してこの値が出現する回数を確認することができます。
2つのパスが必要ですが、データ構造を構築する必要がないため、メモリ使用量が少なくなります。
同等のストリームは、coll.stream().max(...).get(...)
の後にcoll.stream().filter(...).count()
が続きます。
あなたがしようとしているのか本当にわかりません
Comparator
と一致しないequals
の場合のすべての最大値を見つけます。(a)の例は[1, 5, 4, 5, 1, 1] -> [5, 5]
です。
(b)の例は次のとおりです。
Stream.of("Bar", "FOO", "foo", "BAR", "Foo")
.max((s, t) -> s.toLowerCase().compareTo(t.toLowerCase()));
FOO
や[Foo, foo, Foo]
だけではなく、Optional[FOO]
を指定します。
どちらの場合も、1つのパスでそれを行うための巧妙な方法があります。しかし、これらのアプローチは、途中で不要な情報を追跡する必要があるため、疑わしい価値があります。たとえば、[2, 0, 2, 2, 1, 6, 2]
から始める場合、6
に達したときだけ、すべての2
sを追跡する必要がないことに気づくでしょう。
最善のアプローチは明白なものだと思います。 max
を使用し、アイテムを繰り返し処理して、すべてのタイを選択したコレクションに入れます。これは(a)と(b)の両方で機能します。