追追記:もっと簡単でよくね?
下の方の追記部分は、Haskellでやった通り踏襲しようとしすぎてめんどくさいことになってしまったので、もうちょっと簡単にしてみました。
const pred = x => x - 1
const maximum = xs => Math.max(...xs)
const breakByVal = n => xs => {
const foundIndex = xs.indexOf( n )
return [ xs.slice(0, foundIndex ), xs.slice(foundIndex ) ]
}
// 一回分の処理
const predMax = xs => {
const [first, [headSecond, ...tailSecond]] = breakByVal( maximum(xs) )( xs );
return [...first, pred(headSecond), ...tailSecond];
};
//ジェネレーターにする
const predMaxG = xs => function*(){
let ys =[...xs];
while(true) yield (ys = predMax(ys));
}();
//ジェネレーターgをn回分の配列にする関数
const takeArray = g => n => [...new Array(n)].map(_ => g.next().value);
const n = 10;
const seed = [10, 8, 6];
const g = predMaxG( seed );
const a = takeArray( g )( n );
console.log( a );
//=>
[ [ 9, 8, 6 ],
[ 8, 8, 6 ],
[ 7, 8, 6 ],
[ 7, 7, 6 ],
[ 6, 7, 6 ],
[ 6, 6, 6 ],
[ 5, 6, 6 ],
[ 5, 5, 6 ],
[ 5, 5, 5 ],
[ 4, 5, 5 ] ]
以下、過去記事
関数型プログラミングはまず考え方から理解しようを読んで、勉強になったし、自分でもやってみたのでメモ。
コメント欄に自分なりの考えを書きましたが、ひとの記事内であんまりひっぱると失礼かなと思い、これを書いてます。
とはいえ、あんまりよく分っていない自分には、関数型プログラミングとは、とかを語るのは到底無理なので、お題からどう考えたか、を書きます。
#作戦を考える
お題は
唐揚げ弁当がいくつかあるとします。それぞれ唐揚げが複数入っています。
この中からx個の唐揚げをつまみ食いするプログラムを作りましょう。
つまみ食いはバレないようにするために、
その時点で最も唐揚げ数が多いお弁当から取るという仕様にします。
です(元記事より引用)。
これを、
数のリストの一番数の大きい要素から1を引く、ということを繰り返す
ということにしました。
#道具を揃える
haskellでやりますが、haskellのことを良く知りません。
すごいH本は読んだのですがあらかた忘れてます。
で、使えそうな関数をネットであさりました。
- break : 条件でリストを二つに分割してタプルにいれる
- maximum : リストの最大値
- fst : 2要素のタプルの最初の要素
- snd : 2要素のタプルの二番目の要素
- take : リストの最初から指定数分だけ
- pred : ひとつ前の値
これだけあれば何とかなりそう。
あと、関数の作り方、リストの使い方とかも調べました。
#組み立てる
変換したお題
数のリストの一番数の大きい要素から1を引く、
ということを繰り返す
というのをさらに詳しく
数のリストの一番数の大きい要素から1を引く、
ということを無限に繰り返す関数を作り、
最初の10個を計算して表示する
さらに用意した道具(関数)を使って
数のリストから、最大値で分割したリストのタプルを作り、
タプルの一番目のリストと、二番目のリストの初めの要素から1を引いたリストを、結合したリストを作る、
ということを無限に繰り返す関数を作り、
最初の10個を計算して表示する
としました。
できたのがこれです。
predMax xs = zs : predMax zs
where
breakByMax = break (== maximum xs ) xs
y:ys = snd breakByMax
zs = fst breakByMax ++ ( pred y ) : ys
main = print $ take 10 $ predMax [10,8,6]
--実行結果
[[9,8,6],[8,8,6],[7,8,6],[7,7,6],[6,7,6],[6,6,6],[5,6,6],[5,5,6],[5,5,5],[4,5,5]]
#JavaScriptでやってみる
JavaScriptもあんまりよく分ってないのです。
で、ネットであさってみたら、slice
とかconcat
とか使えば何とかなるようでした。
あと、arr.indexOf(Math.max.apply(null, arr))
で配列arrの最大値の要素のインデックスが一発でわかるようでした。
haskellと基本的には同じ作戦です。
ただ、JSだと無限長の配列はだめらしいので再帰を必要分だけまわすように変更しました。
数の配列から、最大値までの配列、最大値一個だけの配列、残りの配列を作り、
一番目の配列と、二番目の配列の初めの要素から1を引いた配列と、三番目の配列とを結合した配列を作る、
ということを指定回繰り返す関数を作り、
10回分を計算して表示する
としました。
できたのがこれです。
(function() {
// window.onload = function(){
predMax = function(xs, n) {
var indexMax = xs.indexOf( Math.max.apply( null, xs ) );
var zs = xs.slice(0, indexMax ).concat( [ xs.slice(indexMax, indexMax + 1 )[0] - 1 ]
, xs.slice(indexMax + 1, xs.length )
);
if(n == 1) return [ zs ];
return [ zs ].concat( predMax( zs, n - 1 ) ) ;
}
console.log(predMax( [ 10, 8, 6 ], 10) );
// }
}())
//実行結果
[ [ 9, 8, 6 ],
[ 8, 8, 6 ],
[ 7, 8, 6 ],
[ 7, 7, 6 ],
[ 6, 7, 6 ],
[ 6, 6, 6 ],
[ 5, 6, 6 ],
[ 5, 5, 6 ],
[ 5, 5, 5 ],
[ 4, 5, 5 ] ]
ちなみに実行にはpaiza.ioを利用させてもらっています。
#まとめ
どちらも興味はあるけど普段さわれてない言語でした。
あらためて調べてみると、結構便利な関数が用意されているし、それらを使うことでよりシンプルに効率良く書けると思いました。(除く学習コスト。調べて理解するのは結構時間がかかる)
一応、関数型プログラミングっていうのは意識してやってます。
自分ではかなり簡潔にかけたなと思っているのですが、何せにわか仕込みの付け焼刃なので、おかしいよ、もっとこんなやりかたがあるよ、っていうのがありましたらコメントおねがいします。
追記:最近の書きかた(ES6)だとこんなかな?
// Haskellっぽくやるための関数定義
const pred = x => x - 1
const fst = xs => xs[0]
const snd = xs => xs[1]
const head = xs => xs[0]
const tail = xs => xs.slice(1)
const maximum = xs => Math.max.apply( null, xs )
const breakF = f=> xs => {
const foundIndex = xs.findIndex( f )
return [ xs.slice(0, foundIndex ), xs.slice(foundIndex ) ]
}
//関数合成のための関数
const pipe = x => (...f) => f.reduce( (acc, e) => e(acc), x )
const dot = (...f) => x => f.reduceRight( (acc, e) => e(acc), x )
//ジェネレーター専用の関数合成のための関数
const pipeG = x => (...f) =>{
const spreadG = xs => [...xs]
const fs =[...f, spreadG]
return fs.reduce( (acc, e) => e(acc), x )
}
//スプレッド構文、メソッドを関数にしておく
const spreadG = xs => [...xs]
const forEachC = f => xs => xs.forEach( f )
// 無限に繰り返して関数をかけ続けるジェネレータ
function nestG( f ) {
return function*( x ){
let y = x
while( true ) {
yield y
y = f( y )
}
}
}
// n 個だけ取り出すジェネレータ
function passTimesG( n ){
return function*( g ){
let x
for(let i = 0; i<n; i++){
x = g.next();
if( x.done ) return;
yield x.value;
}
}
}
// 一回分の処理
const predMax =
xs => {
const breakByMax = breakF( x => x == maximum(xs))( xs );
return [...fst( breakByMax )
, dot(pred, head, snd)( breakByMax )
, ...dot(tail, snd)( breakByMax )
]
}
// [10, 8, 6]を初期値としてpredMaxを適用 > 11回繰り返して停止 > 配列にする > 先頭の要素を取り除く > 出力
pipe( pipeG([ 10, 8, 6 ])( nestG(predMax), passTimesG(11) ) )
( tail, forEachC(e=>console.log(e)) )