5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2018-03-06

関数型プログラミングのすゝめが勉強になったし、後半にある「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]

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

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

5
2
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?