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


Tag: 入出力

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

乱数の生成に、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")

議論