46
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rustでf32やf64の配列の最大値を得る方法

Last updated at Posted at 2016-02-08

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でない小数> のようにして弾くには:

f64をOptionに変換
// `val: f64`: NaNかもしれない値
let opt_f64 = Some(val).into_iter().find(|v| !v.is_nan());
// opt_f64: Option<f64>

最大値と浮動小数点数

Rustでは f32f64PartialOrd を実装してはいるが、 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文を書くのもばかばかしい。

そして、いろいろ試行錯誤した結果、辿り着いた方法が以下のものである。

iter()の場合
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
into_iter()の場合
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であれば、そっちじゃない方を返す」という関数なので、返る値は結局こうなる。

  • 両方 NaNNaN
  • 一方だけ NaNNaN じゃない方
  • 両方 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 を受け取る時点で型を明示したのが max1m の型を明示したのが max2v をイテレータ作成前に明示したのが 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は「ナン」とは発音しないのかもしれない。
残念。

以上。

46
23
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?