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

[関数型プログラミングのすゝめ](https://qiita.com/tail-island/items/1782e4c1e7b620a4f9c0)が勉強になったし、後半にある「Simulaの試着室シミュレーション」というのがおもしろそうなのでやってみた。
[Simula - ウィキペディア](http://www.wikiwand.com/ja/Simula)

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

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

何をどうするものやら皆目見当がつかなかったが、[Javascript:しょうこりもなくFizzBuzz
](https://qiita.com/ttatsf/items/08924fa6675d83b665a8)でいろいろ試していたら、

+ ジェネレータで関数をかけ続け
+ 条件で停止し
+ 結果を配列にし
+ ごにょごにょして
+ 出力

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

```Javascript:
// 繰り返しのためのジェネレータ
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}`)
  )
);
```

```Javascript:
// 結果
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]
```
これでどうでしょう。できてますか?

※ 若干修正しました。大筋は変わりませんが、[関数合成的なこと](https://qiita.com/ttatsf/items/b89794392273ff1e5405)を取り入れてみました。
※ 変遷がありましたが、最終的には[これ](https://qiita.com/ttatsf/items/5fa6e54d734413680128)に則ってます。