Edited at

Javascript:Simulaの試着室シミュレーションをやってみる

More than 1 year has passed since last update.

関数型プログラミングのすゝめが勉強になったし、後半にある「Simulaの試着室シミュレーション」というのがおもしろそうなのでやってみた。

Simula - ウィキペディア


下記の例で Sam、Sally、Andy は服を買おうとしている。彼らは1つの試着室を共有しなければならない。3人は正規分布によりランダムに約12分間店内を探索し、同様に試着室を約3分間占有する。以下は彼らが試着室をどのように使うのかをシミュレーションするものである。


関数型っぽくやるっていうしばりで。

何をどうするものやら皆目見当がつかなかったが、Javascript:しょうこりもなくFizzBuzz

でいろいろ試していたら、


  • ジェネレータで関数をかけ続け

  • 条件で停止し

  • 結果を配列にし

  • ごにょごにょして

  • 出力

という流れで行けんじゃね?と思いたち、やってみました。

// 繰り返しのためのジェネレータ

const nestG = f => {
return function*(x){
let y = x
while( true ) {
yield y
y = f( y )
}
}
}
const passWhileG = f =>{
return function*(g){
let x = g.next()
while( !x.done && f( x.value ) ) {
yield x.value;
x = g.next();
}
}
}
//関数合成のための関数
const pipe = x => (...f) => f.reduce( (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 sortC = f => xs => xs.sort( f )
const filterC = f => xs => xs.filter( f )
const forEachC = f => xs => xs.forEach( f )
// 配列操作
const propArray = prop => xs => xs.map(e => e[prop]) //配列の要素のプロパティの配列
const flatten =
xs => xs.reduce(
(acc, e) => Array.isArray( e ) ? [...acc, ...flatten( e )]
: [...acc, e ]
,[]
) // 多重配列をフラットにする
const maximum = xs => Math.max.apply(null, xs) //要素の最大値
const minimum = xs => Math.min.apply(null, xs) //要素の最小値
// 乱数生成
const genRandNorm = () => Math.sqrt(-2 * Math.log(1 - Math.random())) *
Math.cos(2 * Math.PI * (1 - Math.random()))
const randNorm = (m, s, min, max) => Math.min(Math.max(m + s * genRandNorm(), min), max)
const randShopSpan = () => randNorm(12, 3, 0, 24) // ショッピング時間を生成
const randDressSpan = () => randNorm(3, 1, 0, 6) // 試着時間を生成
// シンボル 兼 表示
const requested = ' requested the fitting room '
const entered = ' entered the fitting room '
const left = ' left the fitting room '
// クラス定義。
// 一人分、一回分のスケジュール。
class Schedule {
constructor(name, reqClock, entClock, leftClock) {
this.name = name;
this.reqClock = reqClock
this.entClock = entClock
this.leftClock = leftClock
}
}
// 一人の1アクション毎のログ。
class Log{
constructor(name, action, clock){
this.name = name;
this.action = action;
this.clock = clock;
}
}
// 一回分のログと全員のスケジュール。
class State{
constructor(logs,schedules){
this.logs = logs
this.schedules = schedules
}
}

//一回分のシミュレーション。一番早くリクエストした人だけ変更。
const moveSim = (state) => {
const shortSpan = 0.001 // アクションの因果関係を保つために挿入
const schedules = state.schedules
const minReqClock = minimum(propArray('reqClock')(schedules));
const activeSchedule = schedules.find( e => e.reqClock == minReqClock);
const otherSchedules = schedules.filter(e => e !== activeSchedule);
const maxLeftClock = maximum(propArray('leftClock')(otherSchedules));
const newEntClock = Math.max(activeSchedule.reqClock, maxLeftClock) + shortSpan;
const newLeftClock = newEntClock + randDressSpan();
const newReqClock = newLeftClock + randShopSpan();
const newLogs = [
new Log(activeSchedule.name, requested, activeSchedule.reqClock)
, new Log(activeSchedule.name, entered, newEntClock)
, new Log(activeSchedule.name, left, newLeftClock)
]
const movedActiveSchedule = new Schedule( activeSchedule.name, newReqClock
, newEntClock, newLeftClock )
const newSchedules = [...otherSchedules, movedActiveSchedule]
return new State(newLogs, newSchedules)
}
// 開始データ
const schedules = [
new Schedule('Sam', randShopSpan(), 0, 0)
, new Schedule('Ally', randShopSpan(), 0, 0)
, new Schedule('Andy', randShopSpan(), 0, 0)
];
const logs = [];
const state = new State( logs, schedules)
const endClock = 100 // 終了時刻
const toMinSec = x =>
`${Math.trunc(x)} m ${Math.trunc(x*6)%6}${Math.trunc(x*60)%10} s` //分秒表示
// シミュレーション継続条件 だれかのリクエスト時刻が n 未満なら継続
const takeCond = n => state => propArray('reqClock' )( state.schedules).some(e => e < n)
const takeCondP = takeCond( endClock ) //終了時刻を固定
// シミュレーション本体。
// 反復 > 条件で停止 > 結果を配列に > logs の配列に > フラット化 > ソート > フィルター > 表示
pipe(
pipeG( state )(nestG( moveSim ), passWhileG( takeCondP ) )
)( propArray( "logs" )
, flatten
, sortC((a, b) => a.clock - b.clock )
, filterC( e => e.clock < endClock ) //終了時刻で制限。reqClock以外がはみだすことがあるので
, forEachC( e => console.log(`${ toMinSec(e.clock) } : ${ e.name }${ e.action }`) )
);

// 結果

8 m 11 s : Ally requested the fitting room
8 m 11 s : Ally entered the fitting room
9 m 57 s : Sam requested the fitting room
11 m 10 s : Andy requested the fitting room
11 m 22 s : Ally left the fitting room
11 m 23 s : Sam entered the fitting room
12 m 34 s : Sam left the fitting room
12 m 34 s : Andy entered the fitting room
14 m 39 s : Andy left the fitting room
25 m 47 s : Ally requested the fitting room
25 m 47 s : Ally entered the fitting room
28 m 21 s : Sam requested the fitting room
28 m 45 s : Andy requested the fitting room
29 m 15 s : Ally left the fitting room
29 m 15 s : Sam entered the fitting room
33 m 37 s : Sam left the fitting room
33 m 37 s : Andy entered the fitting room
38 m 01 s : Andy left the fitting room
41 m 43 s : Ally requested the fitting room
41 m 43 s : Ally entered the fitting room
42 m 26 s : Sam requested the fitting room
43 m 05 s : Ally left the fitting room
43 m 05 s : Sam entered the fitting room
46 m 33 s : Sam left the fitting room
52 m 13 s : Andy requested the fitting room
52 m 13 s : Andy entered the fitting room
53 m 01 s : Sam requested the fitting room
56 m 28 s : Ally requested the fitting room
56 m 32 s : Andy left the fitting room
56 m 32 s : Sam entered the fitting room
58 m 43 s : Sam left the fitting room
58 m 43 s : Ally entered the fitting room
62 m 24 s : Ally left the fitting room
68 m 03 s : Andy requested the fitting room
68 m 03 s : Andy entered the fitting room
70 m 32 s : Andy left the fitting room
70 m 45 s : Sam requested the fitting room
70 m 45 s : Sam entered the fitting room
72 m 58 s : Ally requested the fitting room
76 m 03 s : Sam left the fitting room
76 m 03 s : Ally entered the fitting room
78 m 49 s : Ally left the fitting room
79 m 56 s : Sam requested the fitting room
79 m 56 s : Sam entered the fitting room
80 m 24 s : Andy requested the fitting room
83 m 53 s : Sam left the fitting room
83 m 53 s : Andy entered the fitting room
86 m 32 s : Andy left the fitting room
88 m 47 s : Ally requested the fitting room
88 m 47 s : Ally entered the fitting room
92 m 36 s : Ally left the fitting room
[Finished in 0.477s]

これでどうでしょう。できてますか?

※ 若干修正しました。大筋は変わりませんが、関数合成的なことを取り入れてみました。

※ 変遷がありましたが、最終的にはこれに則ってます。