TL;DR
// iter: Iterator<&f64>
let max = iter.fold(0.0/0.0, |m, v| v.max(m));
// iter: Iterator<f64>
let max = iter.fold(0.0/0.0, f64::max);
// または
let max = iter.fold(0.0/0.0, |m, v| v.max(m));
NaN
かもしれない値を Option<NaNでない小数>
のようにして弾くには:
// `val: f64`: NaNかもしれない値
let opt_f64 = Some(val).into_iter().find(|v| !v.is_nan());
// opt_f64: Option<f64>
最大値と浮動小数点数
Rustでは f32
や f64
は PartialOrd
を実装してはいるが、 Ord
を実装していない。
NAN <= NAN
でなく、かつ NAN > NAN
でもないためだ。
そんなわけで、 Iterator<f32>::max()
なんかは使えないのである。
let vec = vec![1.0, 2.0, -3.0, ::std::f64::NAN, 1.4142, 3.1416, -0.1];
let max = vec.iter().max(); // <- エラー
// "error: the trait `core::cmp::Ord` is not implemented for the type `f64` [E0277]"
かといって、これだけの処理のためにforループとif文を書くのもばかばかしい。
そして、いろいろ試行錯誤した結果、辿り着いた方法が以下のものである。
let vec = vec![1.0, 2.0, -3.0, ::std::f64::NAN, 1.4142, 3.1416, -0.1];
// NaN以外の要素がある場合
let max = vec.iter().fold(0.0/0.0, |m, v| v.max(m));
println!("max: {}", max); // => 3.1416
// NaNしかない場合
println!("max: {}",
Some(::std::f64::NAN).iter().fold(0.0/0.0, |m, v| v.max(m)));
// => NaN
let vec = vec![1.0, 2.0, -3.0, ::std::f64::NAN, 1.4142, 3.1416, -0.1];
// NaN以外の要素がある場合
let max = vec.into_iter().fold(0.0/0.0, f64::max); // <- f64::maxを使う
println!("max: {}", max); // => 3.1416
解説
f64::max
S -> T -> S
として渡した関数 |m, v| v.max(m)
や f64::max
は、「もし引数のどちらかがNaNであれば、そっちじゃない方を返す」という関数なので、返る値は結局こうなる。
- 両方
NaN
→NaN
- 一方だけ
NaN
→NaN
じゃない方 - 両方
NaN
ではない → 大きな方
|m, v| v.max(m)
とクロージャを使うのは理由があって、 vec.iter()
は Iterator<&f64>
とかを返すので、そのまま f64::max
に渡すと、「 f64::max(_, &f64)
なんてねえぞ💢」とコンパイラに怒られるためである。
仕方がないので引数として受け取ってやることになる。
ついでに、 v
は &f64
であるが、メソッドを探す時点で勝手にデリファレンスされて f64::max
が探されるので、 |m, &v|
と書く必要はない。一文字節約だ。
それから、 |m, v| m.max(v)
と逆にすると、これまたうまくいかない。
NAN
として使っている 0.0/0.0
の型が判然としない(f32
とも f64
ともとれる)ため、「 error: no method named `max` found for type `_` in the current scope
」とお叱りを受ける。
型が曖昧だとメソッド max()
を呼べないということは、以下の場合もエラーになる。
let max = vec![0.0, 1.0].iter().fold(0.0/0.0, |m, v| v.max(m)); // ← エラー
こういう場合、 v
側か m
側で、どこかで型をはっきりさせてやる必要がある。
let max1 = vec![0.0, 1.0].iter().fold(0.0/0.0, |m, v: &f64| v.max(m)); // ← おk
let max2 = vec![0.0, 1.0].iter().fold(0.0/0.0f64, |m, &v| m.max(v)); // ← おk
let max3 = vec![0.0f64, 1.0].iter().fold(0.0/0.0, |m, v| v.max(m)); // ← おk
v
を受け取る時点で型を明示したのが max1
、 m
の型を明示したのが max2
、v
をイテレータ作成前に明示したのが max3
である。
max2
の例では、 v
の型が曖昧なため m.max(v)
のように m
のメソッドを呼ぶことと、メソッド呼び出しの引数は勝手にデリファレンスされないため、 &v
のように受け取ることで参照を剥がすこと、この2点に注意だ。
Iterator::fold()
reduceと呼ばれることも多い、例のアレだ。
型は Iterator<T> -> S -> (S -> T -> S) -> S
みたいな感じだが、この用例では S
, T
ともに f64
である。
あとは、 fold()
を、状態として今までの最大値、入力として次の値、出力として更新された最大値のようにして使うことで、イテレータで列挙した要素の最大値が得られる。
最小値
書くまでもないが、 max()
の代わりに min()
を使えばよい。
NaN
の代わりに Option<f64>
が欲しい
ここで if else
なんかを使ってしまうと、一気に文字数が膨れ上がる。
「基本 Some
にしておいて、 NAN
だけ None
にする」という方針でいくと、比較的短く済む。
let max = Some(vec.iter().fold(0.0/0.0, |m, v| v.max(m)))
.filter(|v| !v.is_nan());
手前味噌だが、Rustでイテレータを積極的に使っていくメモ (逐次更新)の事例2である。
その他
ナン(NaN
)をナン(None
)に変換するの、ちょっとおもしろい。
……と思ったが、英語圏では3文字のacronymは一音節で読まず個々の文字を読むと聞いたことがあるので、NaNは「ナン」とは発音しないのかもしれない。
残念。
以上。