ファイルから行をランダムに取り出す - lisp-cookbook-ja/common-lisp GitHub Wiki

入出力

ファイルから行をランダムに取り出す

乱数の生成に、random、ファイルのポジション移動にfile-positionを利用した例

(defun picking-random-nline (nline file)
  (with-open-file (in file)
    (let ((tab (make-hash-table))
          (lnum 0))
      (setf (gethash 0 tab) 0)
      (loop :for c := (read-char in nil) :while c
            :do (when (char= #\Newline c) 
                  (setf (gethash (incf lnum) tab)
                        (file-position in))))
      (flet ((random-line ()
               (file-position in (gethash (random lnum) tab))
               (read-line in nil)))
        (loop :repeat nline :collect (random-line))))))

;; 試してみる
(picking-random-nline 4 "/usr/share/dict/words")
;=> ("whirligig's" "sluicing" "hovercraft" "Sterno")

CLiki:SERIESを使った実装

(in-package :series)

(defun picking-random-nline (nline file)
  (let* ((lines (scan-file file #'read-line))
         (len (collect-length lines))
         (rands (sort (loop :repeat nline :collect (random len)) 
                      #'<)))
    (collect (choose (mask (scan rands)) 
                     lines))))

;; 注. 結果がファイルの先頭からの順番になっています。
(picking-random-nline 4 "/usr/share/dict/words")
;=> ("Chirico's" "ritually" "sweetie's" "sycophant")

ファイルの長さを求めるのにfile-lengthを利用しファイルの長さでランダムに収集した例

行数を知らなくても動作するメリットはありますが、長い行の次の行が出現しやすくなり、行の長さが均一でないとランダムになりません。

(defun picking-random-nline (nline file)
  (with-open-file (in file)
    (let ((len (file-length in)))
      (flet ((random-line ()
               (file-position in (random len))
               (read-line in)           ;捨て
               (or (read-line in nil)
                   (progn (file-position in 0)
                          (read-line in nil)))))
        (loop :repeat nline :collect (random-line))))))

(picking-random-nline 4 "/usr/share/dict/words")
;=> ("tumid" "Judaic" "grownups" "telecommunications")

議論

  • これ、行の長さが同じでないとランダムにならないんじゃないでしょうか。注記があった方が良いのでは (←こういうコメントが邪魔だったら消してください)。
    • おお、気付かれましたか(笑) 長い行の確率が高くなるんですよね。