mapをやろう
リストからリストへ写像して作ろう
プログラミングの鍵は条件分岐
このワークシートはMath by Codeの一部です。
<mapをやろう>
前回の関数型プログラミングの導入では、
反復のfor
条件を手続き型から宣言型に移
し替えることを学んだね。
「n for n in 数の範囲」で数nの「リスト」ができた。geogebraではSequenceコマンドとして作れた。
「2*n+1 for n in 数の範囲」で数nの式を作ると、奇数の「リスト」が作れる。
関数型プログラミングでは
もちろん、
sum(リスト)とすれば、リストの数値の合計が出せる。
find(リスト, "a")などでリストから要素の位置を求めたり、
concat(リスト、リスト)などでリスト連結をしたり、
push(リスト、a)などでリストに要素を追加したりすることはよくあることだね。
それだけではなない。
map(x->f(x),リスト)などとかいて、要素xからf(x)のリストをマッピングできる。
filter(x->expr(x),リスト)などとかいて、要素xのうちexpr(x)が成り立つ要素だけリストができる。
前回は乱数の行列を使って、200行5列の得点表を作ったね。
今回は乱数の2次元配列を作って、
50行5列の得点表を作り、
平均点以上なら+、 未満ならーにマッピングしたい。
juliaでリストのmapやってみよう。
2次元配列全体に対してはマッピングできない。
だから、
1行のデータリストdata[y]にまで入り込んで、
小数得点aveと比べるために得点を小数化したfloat(x)にし、
+-を返す関数updown(x)を作っておき、data[y]を+ーデータとしてmapしよう。
[IN]
#============================================
num = 50
kamoku = 5
data=[[rand(0:100) for x in 1:kamoku] for y in 1:num]
ave= sum([sum(data[y]) for y in 1:num])/(num*kamoku)
updown(x) = float(x) >= ave ? '+' : '-'
res = [ map(x->updown(x) , data[y]) for y in 1:num]
println(res)
#============================================
[OUT]例
[['-', '-', '+', '+', '-'], ['-', '-', '+', '+', '-'], ['-', '+', '+', '+', '-'], ['+', '+', '-', '+', '+'], ['-', '-', '+', '-', '-'], ['-', '-', '+', '-', '+'], ['+', '-', '-', '+', '+'], ['-', '-', '-', '+', '+'], ['-', '+', '-', '-', '-'], ['+', '+', '+', '+', '+'], ['+', '-', '+', '+', '+'], ['+', '-', '-', '+', '+'], ['+', '-', '-', '-', '-'], ['-', '-', '-', '-', '+'], ['+', '-', '+', '+', '+'], ['+', '-', '+', '+', '+'], ['+', '+', '+', '+', '+'], ['-', '-', '-', '-', '-'], ['-', '+', '-', '+', '-'], ['-', '-', '+', '+', '+'], ['+', '-', '+', '+', '+'], ['-', '+', '+', '-', '+'], ['-', '+', '-', '-', '-'], ['-', '+', '-', '+', '-'], ['-', '-', '+', '-', '-'], ['+', '+', '-', '+', '-'], ['+', '+', '+', '+', '+'], ['-', '-', '+', '-', '-'], ['-', '-', '-', '-', '+'], ['-', '+', '+', '-', '+'], ['+', '+', '-', '-', '-'], ['+', '+', '-', '+', '+'], ['+', '+', '-', '+', '+'], ['+', '-', '+', '-', '-'], ['-', '-', '-', '-', '+'], ['+', '+', '+', '-', '-'], ['-', '+', '+', '+', '-'], ['+', '+', '-', '-', '-'], ['-', '+', '+', '+', '-'], ['-', '-', '+', '-', '-'], ['+', '-', '-', '+', '-'], ['+', '-', '+', '-', '-'], ['-', '+', '+', '-', '-'], ['+', '-', '-', '+', '-'], ['-', '-', '+', '-', '+'], ['-', '+', '-', '+', '-'], ['+', '+', '-', '-', '+'], ['+', '+', '+', '-', '-'], ['+', '-', '+', '+', '-'], ['-', '-', '-', '+', '+']]
残念だが、geogebraにはmapコマンドそのものはない。
しかし、if文は使えるので、updown(x)という3項演算子のような関数は使わずに、
直接sequenceコマンドにifを入れ込んでみよう。
データ要素と平均を比べる場面では、Elementコマンドを使うとインデックス指定で
データが取り出せるね。
[IN]
#============================================
num = 50
kamoku = 5
data=Sequence(Sequence(RandomBetween(0,100),n,1,kamoku),m,1,num)
ave=((Sum(Sequence(Sum(data(n)),n,1,num)))/(num kamoku))
rs=Sequence(Sequence(If(Element(data,m,n)≥ave,"+","-"),n,1,kamoku),m,1,num)
#============================================数のリストから記号のリストが作れちゃう
質問:rustを使って、関数型のコードはかけますか。
はい。かけます。
関数型も手続き型もどっちもいけます。
rustは最後のセミコロンあるものが文でないと式になります。
しかも、if構文はif式なので、
let A= ifナントカ;
という文で、変数Aにifで判断した結果をセットできます。
また、python,juliaのリストにあたるのがVectorです。固定サイズの配列とちがって要素の追加・削除が
できます。それに,python,julia,haskellのように関数型でかけるのに、インデントの場所は自由にできます。
しかし、haskellとちがって、ふつうに反復構文(while, loopとか)が使えるので、
純粋な関数型言語とくらべると、自由度が高いですね。
ただし、スピードとメモリの安全性を優先するので、型をできるだけ宣言しておきましょう。
rustのインストール方法はご自身で検索してください。
注意点は、パッケージの更新がとても速いので、バージョンがすぐにあがること。
コマンドラインから
cargo new lst
cd lst
とします。VScodeなどでlstフォルダを見ましょう。
乱数を使うためパッケージ(rustではクレートという)の記入しておきます。
Cargo.tomlの依存ファイルのところにパッケージ名="バージョン"を追加しておきます。
0.9.2が2025の1月で最新ですが、今後上がるでしょう。それにあわせて、書きましょう。
最悪、それにともなって、関数名まで古くなって新品に交換しないと動かなくなるかもしれません。
[dependencies]
rand = "0.9"
次に、srcフォルダにあるmain.rsの中身を次のように貼りかえましょう。
//[IN]rust
use rand::{Rng};//乱数を使うためのクレートと関数をかく
fn main() {
//50行5列の0以上100以下の乱数の得点表を作る。
//平均点以上なら+、 未満ならーにマッピングしたい。
let num = 50;
let kamoku = 5;
let mut rng = rand::rng();//乱数のもとを作る。
//データ作成***************************************
let data:Vec< Vec < i32 > > =
(0..num).map(|_|
{(0..kamoku).map(|_| rng.random_range(0..=100)).collect()}
//kamoku数だけ発生してあつめる。
).collect(); //num数だけ発生してあつめる。
let total: i32 = data.iter().map(
|row| row.iter().sum::() // 科目合計
).sum(); // 合計の合計
let ave = total as f64 / (num * kamoku) as f64;//平均の計算
let updown = |x: i32| -> char { if (x as f64) >= ave { '+' } else { '-' } };
let table: Vec< Vec < char > > =
data.iter().map(|row|
{row.iter().map(|&x| updown(x)) .collect()}//行の要素を文字にする
).collect();//各行あつめる。
println!("{:?}",table);
}
出力は、
コマンドラインにコンパイルと実行を両方やり、メッセージを最小にすると、juliaと同様の結果になります。
cargo run --quiet
[['-', '-', '-', '-', '+'], ['-', '-', '+', '+', '-'], ['+', '-', '+', '+', '-'], ['-', '+', '-', '-', '-'], ['-', '-', '-', '-', '+'], ['-', '+', '-', '-', '+'], ['-', '-', '+', '+', '+'], ['+', '-', '-', '+', '+'], ['+', '+', '+', '-', '-'], ['+', '-', '-', '+', '+'], ['+', '+', '-', '+', '+'], ['+', '+', '+', '+', '+'], ['-', '-', '+', '+', '-'], ['-', '-', '+', '-', '+'], ['+', '-', '-', '-', '+'], ['-', '-', '+', '-', '+'], ['-', '+', '-', '-', '-'], ['-', '-', '+', '-', '-'], ['+', '+', '+', '+', '+'], ['
rustの構文の特徴を確認しておきましょう。
・juliaでは、f(x) for x in リスト、f(x) for x in 範囲、というように要素の操作のあとに対象をかきました。
rustでは、
範囲.map(|x| f(x))とか、
リスト.iter().map(|x| f(x)}というように、
対象のあとに操作をかきます。
また、rubyのように対象となる要素xには|x|のように、絶対値マークをつけます。
それに、対象要素xを不定マーク(_)にすることができます。すると、
範囲.map(|_| f(x))は、f(x)を範囲となる回数を繰り返すという意味になります。
また、注意しなければならないのは、
juliaではmapですぐにリストができたのに、
pythonではlist()でかこまないとリストができなかったと同じように、
操作した結果をあつめてリストにしますよという指示、.collect()をつけないといけないということです。
map()とか、sum()とかで対象をかこむjuliaの書き方とちがい、
リスト.iter().map(要素と操作).collect()
リスト.iter().sum(要素と操作)
のようにイテレーション(走査)の機能として末尾に命令をたしてく形式です。
書き方が逆ですね。
また、rustは,
let 変数::型名=式;
という形で変数を型名を明確にして束縛します。メモリを確実に確保するためです。
もちろん、型推論という機能もあるので、忘れても平気なときもありますが、
変数を組み合わせて演算していくうちに、型を変換する羽目に陥るときがあります。
そのときは、
変数 as 変換先の型名
という書き方をしてください。
変換しないと仕方ないときがあります。
整数A÷整数Bは整数の範囲の商になってしまうので、
A as f64 / B as f64のようにして商が小数になるようにしましょう。
大きいサイズを小さく変換すると
内容によっては変わってしまうこともあるので、
プログラムは動いても、内容がおかしくなることもありうるので注意しましょう。
また、一般的にrustの書籍は先端を進んでいるという感じの方がかいているので、
硬い用語をそのまま、あえて使っていることが多い気がします。
pythonよりはまだ一般化してないからです。
そういうものだという気持ちで情報と接することが大切ですね。
また、プリント文にも注意しましょう。
!マークは関数ではなくマクロというものだという目印です。
それは約束事だからいいいのですが、{:?}が気になりますね。
tableは表だから、ベクターのベクターです。
だから、その型名にあう出力が必要になりますが、とりあえずデータを押し出したいときは
{}ではあなく、{:?}にすると形の崩れたままでよければ出力できる便利な指定です。
println!("{:?}",table);