関数型プログラミングのすゝめが勉強になったし、後半にある「Simulaの試着室シミュレーション」というのがおもしろそうなのでやってみた。
Simula - ウィキペディア
下記の例で Sam、Sally、Andy は服を買おうとしている。彼らは1つの試着室を共有しなければならない。3人は正規分布によりランダムに約12分間店内を探索し、同様に試着室を約3分間占有する。以下は彼らが試着室をどのように使うのかをシミュレーションするものである。
関数型っぽくやるっていうしばりで。
何をどうするものやら皆目見当がつかなかったが、Javascript:しょうこりもなくFizzBuzz
でいろいろ試していたら、
- ジェネレータで関数をかけ続け
- 条件で停止し
- 結果を配列にし
- ごにょごにょして
- 出力
という流れで行けんじゃね?と思いたち、やってみました。
// 繰り返しのためのジェネレータ
const nestG = f => x => function*() {
let y = x;
while (true) {
yield y;
y = f(y);
}
}()
const passWhileG = f => g => function*() {
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 => (...fs) => {
const spreadG = xs => [...xs];
const fss = [...fs, spreadG];
return fss.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 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 = Math.min(...propArray("reqClock")(schedules));
const activeSchedule = schedules.find(e => e.reqClock == minReqClock);
const otherSchedules = schedules.filter(e => e !== activeSchedule);
const maxLeftClock = Math.max(...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]
これでどうでしょう。できてますか?
※ 若干修正しました。大筋は変わりませんが、関数合成的なことを取り入れてみました。
※ 変遷がありましたが、最終的にはこれに則ってます。