Lisp を使った日常計算
Lisp は難しいという印象があるようだが、日常の計算くらいなら簡単にできるぞという記事。
タイミーの Good 率を求める
私はタイミーをやっているんですけども、タイミーって Good 率で申込みが足切りされることがあるんですね。
すると Good 率が気になってきます。
なのでこの記事では
- Good 率と Good 数の関係を調べる
- Good 率が N% 増えたがこれは Good がいくつ増えた のか計算する
- Good 率があとどれくらいで 100% になるのか計算する
- 見やすいように表にする
をしていきます。
前提
- タイミーの Good 率は直近 30 回で計算される
- キュー(FILO)
- 実は事業者は評価しなくても良いのだが、話が複雑になるので必ず Good or Bad の評価をするものとする
- どの事業者が Bad 評価したのかは確認できない
リスト
(define history-max 30)
(define make-table
(lambda ()
(let loop ((history history-max)
(good 1)
(good-rate-table '()))
(if (< history good)
good-rate-table
(loop history (+ good 1) (cons
(cons good (ceiling (* (inexact (/ good history)) 100)))
good-rate-table))))))
;; ((30 . 100.0) (29 . 97.0) (28 . 94.0) (27 . 90.0) (26 . 87.0) ...
タイミーのアプリでは小数点以下は丸められています。どう丸めているかはよくわかりませんが、直近で自分の Good 率が 87 -> 94 に改善したため、とりあえず自分のログとは矛盾していないです。
整形
Good 数と率の対応リストを作りましたがこのままだと見づらいので整形します。
ついでに Good 数がいくつ増えたかも計算してみます。
(define table (make-table))
(define (print-rate-table table)
(format #t "Good | Rate~%")
(format #t "----------------~%")
(for-each
(lambda (entry)
(format #t "~2d | ~6,1f%~%"
(car entry)
(cdr entry)))
table))
(print-rate-table table)
(newline)
(define (print-rate-markdown-table table)
(format #t "|Good | Rate|~%")
(format #t "|---------|-------|~%")
(for-each
(lambda (entry)
(format #t "|~2d | ~6,1f%|~%"
(car entry)
(cdr entry)))
table))
(print-rate-markdown-table table)
(newline)
;; Good | Rate
;; ----------------
;; 30 | 100.0%
;; 29 | 97.0%
;; 28 | 94.0%
;; 27 | 90.0%
;; 26 | 87.0%
;; 25 | 84.0%
;; 〜〜〜〜〜〜
;; 5 | 17.0%
;; 4 | 14.0%
;; 3 | 10.0%
;; 2 | 7.0%
;; 1 | 4.0%
;;
(format #t "増えた Good 数: ~2d~%"
(let ((before (rassoc 87.0 table))
(after (rassoc 94.0 table)))
(- (car after) (car before))))
;; 増えた Good 数: 2
だいぶ良くなりました。
ただ 30 もカラムがあると縦に積むと Qiita だと長くて見づらいので横に畳んだ表を Markdown で作ります。
(define (print-rate-markdown-table-folded table)
(let* ((groups (chunks table 10))
(cols (length groups))
(max-len (apply max (map length groups))))
;; ヘッダ
(display "|")
(do ((i 0 (+ i 1)))
((= i cols))
(display " Good | Rate |"))
(newline)
;; 区切り線
(display "|")
(do ((i 0 (+ i 1)))
((= i cols))
(display "------|------|"))
(newline)
;; データ行
(do ((row 0 (+ row 1)))
((= row max-len))
(display "|")
(for-each
(lambda (grp)
(if (< row (length grp))
(let ((entry (list-ref grp row)))
(format #t " ~2d | ~3d% |"
(car entry)
(inexact->exact (round (cdr entry)))))
(display " | |")))
groups)
(newline))))
(print-rate-markdown-table-folded table)
これで次のような表ができます。
| Good | Rate | Good | Rate | Good | Rate |
|---|---|---|---|---|---|
| 30 | 100.0% | 20 | 67.0% | 10 | 34.0% |
| 29 | 97.0% | 19 | 64.0% | 9 | 30.0% |
| 28 | 94.0% | 18 | 60.0% | 8 | 27.0% |
| 27 | 90.0% | 17 | 57.0% | 7 | 24.0% |
| 26 | 87.0% | 16 | 54.0% | 6 | 20.0% |
| 25 | 84.0% | 15 | 50.0% | 5 | 17.0% |
| 24 | 80.0% | 14 | 47.0% | 4 | 14.0% |
| 23 | 77.0% | 13 | 44.0% | 3 | 10.0% |
| 22 | 74.0% | 12 | 40.0% | 2 | 7.0% |
| 21 | 70.0% | 11 | 37.0% | 1 | 4.0% |
かなり見やすくなりましたね。
100 %まであと何回?
表から、あと 2 回 Good を積み増せば 100% になることがわかります。
キュー内の Bad を追い出す必要がありますが、どの事業者が Bad 評価を付けたかは分からないため、キューの中身は把握できません。したがって、もし今後すべての案件で Good 評価を続けられた最良のケースでは、最悪 30 回で 100% に到達する、ということしか断言できません。
終わりに
重要なのはループで印字するのではなくデータ構造を作ることです。
データさえあれば後でいくらでも整形できるからです。