質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

3回答

4585閲覧

【Java】Listから最小値を取得するストリームについて

kazu0630

総合スコア26

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

0クリップ

投稿2020/04/16 10:42

編集2020/04/16 10:42

OCJP Goldの学習をしております。

以下のコードで、最小値50が出力されない理由をご教示いただきたいです。

Comparatorの内部にあるreturn文の直前で戻り値を出力した際は、50が出力されましたが、実際に、以下のコードでは200が出力されました。

Java

1import java.util.Arrays; 2import java.util.Comparator; 3import java.util.List; 4 5public class Test { 6 7 public static void main(String[] args) { 8 List<Integer> nums = Arrays.asList(100,50,200); 9 10 Comparator<Integer> com = new Comparator<Integer>() { 11 public int compare(Integer o1,Integer o2) { 12 return Integer.min(o1, o2); 13 } 14 }; 15 System.out.println(nums.stream().min(com).get()); 16 } 17 18}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

端的にいうと、Comparatorの実装のしかたが間違っています。

compareは、1か0か-1を返す実装をします。

最初の引数が2番目の引数より小さい場合は負の整数、両方が等しい場合は0、最初の引数が2番目の引数より大きい場合は正の整数。

minを返す現在の実装の場合、要素に負数がないListの場合、必ず比較の後者が小さいと判定されます。
そのためListの要素に正の整数しかない場合、必ず要素の最後がminと判断されるはずです。


追記

コメントのやりとり見ていると理解済みかと思いますが、

Listに1以上の値しかないというのは、第1引数と第2引数の値の比較結果のことでしょうか。

return Integer.min(o1, o2);⇒必ず50が返る
compareメソッドの特性により、50ではなく、正の数が返る

Listに1以上の値しかない

要素の値についてです。
要素に正の整数しかないなら、minの結果が必ず正の整数になり、必ず第一引数が大きいと判断されます。

compareメソッドの特性により、50ではなく、正の数が返る

特性ではありません。実際に50を返していますし、50は正の数です。
繰り返しになりますが、compareの実装は

最初の引数が2番目の引数より小さい場合は負の整数、両方が等しい場合は0、最初の引数が2番目の引数より大きい場合は正の整数。

のように実装する必要があります。

すでにいくつか試されているようですが、Listの要素を2つにして単純な比較の方が動作を理解しやすいかと思います。
現在の実装だと(1,2)と(2,1)のような要素の順序により期待の結果にならないことが容易にわかるはずです。

投稿2020/04/16 10:52

編集2020/04/17 02:10
momon-ga

総合スコア4820

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kazu0630

2020/04/16 11:14

ご回答いただき、ありがとうございます。 >minを返す現在の実装の場合、要素に負数がないListの場合、必ず比較の後者が小さいと判定されます。 >そのためListに1以上の値しかない場合、必ず要素の最後がminと判断されるはずです。 Listに1以上の値しかないというのは、第1引数と第2引数の値の比較結果のことでしょうか。 return Integer.min(o1, o2);⇒必ず50が返る compareメソッドの特性により、50ではなく、正の数が返る ということでしょうか。 その場合、「最初の引数が2番目の引数より大きい場合は正の整数。」に該当するため、必ず要素の最後が最小値と判断されませんよね・・。 申し訳ありませんが、もう少しご教示願えますでしょうか。
jimbe

2020/04/16 11:29

compare メソッドの引数にどの値が入れられてくるかは仕様上決まっていなかったように思います. ご提示のコード・データではどんな値が来ても常に正数が返り, つまり o1 が大きい(=o2が小さい) と判定されるため, 大小関係に一貫性が無く, ソートの実装によって結果が変わる可能性があります.
kazu0630

2020/04/16 11:54 編集

jimbeさん、いつもご回答いただき、ありがとうございます。 ①「return Integer.min(o1, o2);」により、o1=100 o2=50で、50を返す ②「compareメソッド」により、正数が返る(o1>o2と扱われる) ③「return Integer.min(o1, o2);」により、o1=50 o2=200で、50を返す ④「compareメソッド」により、正数が返る(o1>o2と扱われる) ⑤最小値が「④のo2」と判断される(一番最後の要素が最小値と扱われる) ということでしょうか。
xebme

2020/04/16 13:04

jimbeさんはソートのことをお考えですか。クイックソートだとわかりません。でもこの質問では順次アクセスしか考えられません。.peek(System.out::println)を入れて確認してください。 nums.stream().peek(System.out::println).min(com).get() min()は順次アクセスのreduce()と同じことをしているのではないでしょうか。 nums.stream().reduce((x,y)->(Integer.compare(x,y)>0)?y:x).get()
kazu0630

2020/04/16 14:10

xebmeさん、ご回答いただき、ありがとうございます。 peekメソッドを入れた結果、以下のようにリストの格納順+一番最後の要素が出力されました。 ■結果 100 50 200 200 ちなみにリストの要素を変えてみたところ、以下のようになりましたが、結果に納得ができない状態が続いております・・。 ■リストの要素 -100,50,200 ■結果 -100 50 200 -100 ■リストの要素 100,-50,200 ■結果 100 -50 200 200 ■リストの要素 100,50,-200 ■結果 100 50 -200 50
xebme

2020/04/16 14:50

おかしくない。Integer.min()がComparator<Integer>.compare()とみなされている。 ■リストの要素 -100,50,200 ■結果 min(-100,50)=-100(負)->第一引数が小さい(-100を次の第一引数に), min(-100,200)=-100(負)->第一引数が小さい(-100) ■リストの要素 100,-50,200 ■結果 min(100,-50)=-50(負)->第一引数が小さい(100を次の第一引数に), min(100,200)=100(正)->第二引数が小さい(200) ■リストの要素 100,50,-200 ■結果 min(100,50)=50(正)->第二引数が小さい(50を次の第一引数に), min(50,-200)=-200(負)->第一引数が小さい(50) この動作は reduce()とまったく同じ、順次処理ですね。
kazu0630

2020/04/16 15:23 編集

xebmeさん、ご回答いただき、ありがとうございます。 2つ目について、質問させてください。 ■リストの要素 100,-50,200 ■結果 min(100,-50)=-50(負)->第一引数が小さい(100を次の第一引数に), min(100,200)=100(正)->第二引数が小さい(200) は、 min(100,-50)=-50(負)->第二引数が小さい(-50を次の第一引数に), min(-50,200)=-50(負)->第一引数が小さい(-50) ※(100と-50の比較では、-50の方が小さい⇒第二引数の方が小さい、-50と200の比較では、-50の方が小さい⇒第一引数の方が小さい) となるように考えましたが、お答えいただいた内容と異なります。 お手数をおかけ致しますが、もう少しご教示願えますでしょうか?
swordone

2020/04/16 15:48

だから、minで何が返ってくるかなんて関係ない。問題になるのはcompareが返す値の符号のみ。負なら第一引数が、正なら第二引数が小さいと判定される。それだけ。
jimbe

2020/04/16 17:34

すいません, compare なのでついソートと思ってしまいましたが, min でした.
momon-ga

2020/04/17 02:11

swordoneさんのいう通り > 問題になるのはcompareが返す値の符号のみ。負なら第一引数が、正なら第二引数が小さいと判定される。それだけ。 本当に、これだけなんです。なので正しい実装をすれば期待通りになります。
kazu0630

2020/04/17 03:37

ご回答いただきました皆様、ありがとうございます。 教えていただきました内容と自分でいろいろと試していく中で、理解ができたと思います。 皆様、丁寧にコメントをいただいたので、皆様のコメントをベストアンサーにしたかったのですが、それができないので、一番コメントをいただいたこの回答をベストアンサーとさせていただきます。 以下の内容は、自分が正解だと思った内容のメモです。 ■100,50,200 100と50の比較⇒minにより、50が返る⇒戻り値50の値は正なので、compareメソッドは正の値を返す⇒第二引数(右側)の値が返る 50と200の比較⇒minにより、50が返る⇒戻り値50の値は正なので、compareメソッドは正の値を返す⇒第二引数(右側)の値が返る ■100,50,-200 100と50の比較⇒minにより、50が返る⇒戻り値50の値は正なので、compareメソッドは正の値を返す⇒第二引数(右側)の値が返る 50と-200の比較⇒minにより、-200が返る⇒戻り値-200の値は負なので、compareメソッドは負の値を返す⇒第一引数(左側)の値が返る ■100,-50,200 100と-50の比較⇒minにより、-50が返る⇒戻り値-50の値は負なので、compareメソッドは負の値を返す⇒第一引数(左側)の値が返る 100と200の比較⇒minにより、100が返る⇒戻り値100の値は正なので、compareメソッドは正の値を返す⇒第二引数(右側)の値が返る ■-100,50,200 -100と50の比較⇒minにより、-100が返る⇒戻り値-100の値は負なので、compareメソッドは負の値を返す⇒第一引数(左側)の値が返る -100と200の比較⇒minにより、-100が返る⇒戻り値-100の値は負なので、compareメソッドは負の値を返す⇒第一引数(左側)の値が返る
guest

0

nums.stream().min(com)でやっていることは、ざっくり言うとこういうことなんです。
(細かいところはいろいろ違うので、あくまでイメージとしてとらえてください)

java

1Integer min = nums.get(0); 2for (Integer i : nums) { 3 min = com.compare(min, i) < 0 ? min : i; 4} 5return min;

compareの中で、2つ比較して小さいほうを返したとて、このメソッドはそれをそのまま「小さいほうの値」とは解釈しません。あくまでcompareの結果として、符号からどちらが大きいかを判定します。

投稿2020/04/17 02:39

swordone

総合スコア20651

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

すでに正解が出ているので、あくまでもmomon-gaさんの回答の補足説明として読んでください。

正か、負か、それが問題だ

Comparaterの役割は、正、0 、負、によって二つの引数の大小同値を判定すること。数値型の場合、計算で判定できます。

(x,y) -> x - y

Streamのmin()/max()/sum()

これらはどれも reduce() で書き換えることができます。リダクション(畳み込み)と呼ばれる操作で累積的な結果を返します。reduce()のなかのラムダ式を見てください。

reduce((x,y) -> 何らかの結合的な操作を実行して「累積的な値」を返す)

ラムダ式の引数に役割があります。引数 x は「先頭の要素」か「累積的な値」、y は順次処理される「個別の要素」です。操作によって「累積的な値」 の意味は以下のように異なります。どれも y の値を使って、x の値を更新します。

  • min の場合は、x は最小値
    x と y を比較して小さい値を返す。返された値が次の x の値として使われる。
    比較はComparatorで行ってもよい。
  • max の場合は、x は最大値
    x と y を比較して大きい値を返す。返された値が次の x の値として使われる。
    比較はComparatorで行ってもよい。(minと同じComparatorです)
  • sum の場合は、x は合計値
    x に y を加算した値を返す。返された値が次の x の値として使われる。

内部動作としてこのことを理解してください。

リダクションを使う最小値の取得

リダクションでは、Integer::minを指定すれば目的の動作が行われます。以下、listの型はList<Integer>です。

Java

1list.stream().reduce((x,y)->(Integer.compare(x,y) <= 0) ? x : y).get(); 2list.stream().reduce(Integer::min).get();

min()を使う最小値の取得

最初に説明したとおり、Comparatorを渡さなければなりません。

Java

1list.stream().min((x,y)-> x - y).get(); 2list.stream().min(Integer::compare).get();

間違いの解説

以下の最初の行がComparatorの代わりにInteger.minを指定する間違いです。これと等価なのが第二行目のreduceです。minの結果は正負判定に使われています。どちらも同じ動作であることを確認してください。

Java

1list.stream().min(Integer::min).get(); 2list.stream().reduce((x,y)->(Integer.min(x,y) <= 0) ? x : y).get();

投稿2020/04/17 00:08

編集2020/04/17 00:26
xebme

総合スコア1083

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問