「フォークじゃない」をFactorで(横へな18)


フォークじゃない」をFactorでやってみました。他の皆様による実装は第18回オフラインリアルタイムどう書くの問題から辿れます。

時間が経つと自分でもコードが理解できなくなるため、今回は日本語Mind風のコメントを付けてみました。こうやってコメントを揃えると、ますます高階関数付きのアセンブリ言語という感じです。

18notfork.factor
USING: arrays generalizations kernel math
       math.parser sequences sorting strings ;
IN: 18notfork

SYMBOL: o                               ! 客を表す記号
SYMBOL: x                               ! 絶望的に長くかかる客を表す記号

: throughput ( -- seq ) { 2 7 3 5 2 } ; ! 各レジの処理能力

: shortest ( regi -- n )                ! 最も短いレジを探す とは
    [ length ] map                      ! 各レジの長さを
    [                                   !
        natural-sort                    ! 昇順ソートして
        first                           ! 先頭をとることを
    ] keep                              ! 各レジの長さを保存してから実行し
    index ;                             ! その位置を返すこと。

: string>customer ( 1str -- seq )       ! 文字をお客に展開する とは
    dup "x" =                           ! 文字が"x"なら
    [ drop { x } ]                      ! xを、
    [ string>number o <array> ] if ;    ! それ以外ならoを人数分返すこと。

: append-nth ( a n seq -- seq' )        ! 列に追加する とは
    [                                   !
        pick = [                        ! 指定された位置だったら
            pick append                 ! 追加することを
        ] when                          !
    ] map-index                         ! すべての要素に繰り返し
    2nip ;                              ! 位置を消去すること。

: queue ( regi 1str -- regi' )          ! 並ばせる とは
    string>customer                     ! 文字をお客に展開したものを
    swap [ shortest ] keep              ! 最も短いレジの
    append-nth ;                        ! 列に追加すること。

: check-one ( seq -- seq' )             ! 会計を1人ずつ進める とは
    dup length 0 > [                    ! 誰かが並んでいて
        dup first x = not [             ! 先頭がxでなければ
            rest                        ! 1人進めること。
        ] when                          !
    ] when ;                            !

: check ( regi -- regi' )               ! 会計を進める とは
    throughput [                        ! 処理能力の分だけ
       [ check-one ] times              ! 会計を1人ずつ進めることを
    ] 2map ;                            ! 繰り返すこと。

: simulate ( regi input -- regi' )      ! シミュレートする とは
    [                                   !
        1string dup                     ! 入力を1文字取って
        "." =                           ! "."なら
        [ drop check ]                  ! 会計を進め
        [ queue ] if                    ! それ以外なら並ばせることを
    ] each ;                            ! 繰り返すこと。

: to_s ( regi -- str )                  ! 文字列に変換する とは
    [                                   ! レジの
        length                          ! 長さを
        number>string                   ! 文字列にすることを
    ] map                               ! 全レジに対して実行し
    "," join ;                          ! ","で連結すること。

: solve ( str -- str )                  ! 解く とは
    5 { } <array>                       ! 誰も並んでいないレジを用意して
    swap simulate                       ! シミュレートし
    to_s ;                              ! 結果を文字列に変換すること。

"42873x.3." solve を実行すると、結果の "0,4,2,0,0" がスタックに積まれます。Factorはワードを使う前に定義する必要があるため、solveは最後(他のワードより後)に定義されています。

多少はFactorに慣れてきました。mapやeachなどのシーケンスコンビネータを使うとき、各イテレーションで渡されるエレメントの前のスタック内容が維持されていることがわかりました。たとえば
4 5 { 1 2 3 }
というスタックがあるとき、mapやeachを使うと
4 5 1 4 5 2 4 5 3
というスタック状態で3回のイテレーションが発生します。それぞれのエレメント(1,2,3)の前のスタック状態 4 5 は維持されています。このことがわかったおかげで、前回よりもすっきりしたコードが書けました。