← Архив: Common Lisp

Первая программа на CL, нужен совет по чтению бинарных файлов

Author: · 30.12.2010 14:21
· original author: shamaz.mazum
Привет, решил поближе изучить common lisp. Использую реализацию clisp под DragonFlyBSD (то, о чём я буду писать ниже, будет касаться и linux)
Написал тут свою программку для проверки запоминания иностранных слов.
Тестовый файл:
http://shamazmazum.users.sourceforge.net/test
Прога:
http://shamazmazum.users.sourceforge.net/mnemonic.tar
Принцип прост: составляем файл вида:
форма_слова11 - перевод
форма_слова12 - перевод
....
%
форма_слова21 - перевод
форма_слова22 - перевод
....
итд.
Запускаем strfile имя_файла и получаем бинарник, используемый для произвольного чтения строк. (Как раз эта программа ещё используется для фортунок, которые тоже состоят из строк, разделенных символом %)
Дальше запускаем программу (нужен asdf, cl-gtk2, cl-utilities): (asdf:load-system :mnemonic) (mnemonic:run),
выбираем файл словаря (test) и индекс для произвольного доступа (test.dat) и нажимаем "Start test". В общем, легко разобраться)
Теперь, собственно, вопрос:
У программы есть недостаток (которого бы не было, если бы я выбрал C:) - это то, что в 32 и 64 битных системах тип в C long имеет разную длину (ничего не путаю? 4 против 8), а strfile как раз хранит значения смешений и значения в шапке в unsigned long, а fseek их принимает:
http://www.cppreference.com/wiki/io/c/fseek
http://pwet.fr/man/linux/commandes/strfile
Так как же заставить работать программу и под 32- и под 64-битные системы? Пока можно изменить значение переменной где-то в dictio.lisp, но это ведь не выход!
Также интересует возможность читать файлы относительно большими блоками (так ведь быстрее, да?). Пока я делаю так:
(let ((a (open file-name :direction :input :element-type (list 'unsigned-byte (* 8 *long-size*))))) (read-byte a))
Интересно, что будет, если я укажу большое значение с unsigned-byte, и read-byte пока не достигнет конца файла, но уже и не сможет прочитать такой большой блок. Похоже, если я всё правильно делаю, вернет ошибку. А до конца высосать файл как-то можно?
· original author: allchemist
Лисп позволяет делать многие вещи существенно проще, чем в Си.
В частности, т.к. это не бинарный файл, и можно читать его не как byte-stream, а как character-stream, то есть посимвольно, а не побитно.
Тогда отдельную строку можно прочитать так:
(with-open-file (s /path/to/dict")
  (read-line s))

Теперь строка содержит слово и через черточку его перевод. Функция split-sequence (из соотв. пакета) разделит эту строку на две, а string-trim (из стандарта) обрежет лишние пробелы по бокам. Чтобы получить список из фразы и ее перевода:
(mapcar #'(lambda (x) (string-trim " " x))
        (split-sequence:split-sequence #\- line)
)
))
Чтобы иметь возможность перемещаться к началу произвольной строки, можно сделать все строки в файле одинковой длины, заполнив остатки пробелами. Файл вряд ли вырастет сильно в размере. Кстати, как сейчас предполагается переходить к произвольной строке?
Если так, то, зная длину строки, легко переместиться к определенной строке с помощью file-position. В итоге функция для получения произвольной (n-ной) пары фраза-перевод может пригодяться такая функция:
(defun training-pair (n dict-file)
  (with-open-file (s dict-file)
    (file-position s (* n *string-length*))
    (multiple-value-bind (line if-eof)
        (read-line s nil nil)
      (if if-eof
          (warn "Index ~A is too much" n)
          (mapcar #'(lambda (x) (string-trim " " x))
                  (split-sequence:split-sequence #\- line)
)
)
)
)
)

Помимо прочего, read-line может проверять, достигнут ли конец файла. Для этого она возвращает второй аргумент, если он t, значит, достигнут конец и индекс n больше реального количества строк. Чтобы поймать сразу несколько возвращаемых значений, используем multiple-value-bind.
Как-то так. Надеюсь, я правильно понял задачу.
· original author: allchemist
А вообще, еще лучше не связываться с файловым io каждый раз при получении обучающей пары.Словарь можно сконвертить в тот же ассоциативный список и загружать его в память при каждой загрузке (или загружать сохраненный на диске с помощью cl-store образ этого списка). Хотя хз, если он очень большой будет. Словарь то есть. Можно с разбиением его на части что-то придумать.
· original author: shamaz.mazum
> В частности, т.к. это не бинарный файл, и можно читать его не как byte-stream, а как character-stream, то есть посимвольно, а не побитно.Тогда отдельную строку можно прочитать так:
Ну сам словарь я читаю построчно с помощью read-line и открываю как обыкновенный текстовый файл (с element-type по умолчанию). Индексы с началом нового слова я считываю с бинарного файла (который дает strfile) в лист, передаю произвольный элемент file-position
Да, чтобы было понятно, индексы я считываю только для того, чтобы проверяемые слова шли вразнобой. И именно по этому не считываю весь текстовый файл сразу: боюсь, это будет медленно.
Более подробно чтение индексов (offsets не очень уж корректное название, т.к. все они - относительно начала файла; может быть не понятно без прочтения мана по strfile):
defun read-offsets (name)
 ;; Check if this file is correct index file
 (let* ((test-stream (open name :direction :input))
         (size (file-length test-stream))
)

    ;; Size = size-header_size
   (close test-stream)
    (setq size (- size (* 6 *long-size*)))
    (if (not (= 0 (mod size *long-size*)))
        (error 'not-an-index-file
                 :file-name name
)
)
)

 
 (let (str-numstr offsets
        (stream (open name :direction :input :element-type (list 'unsigned-byte (* 8 *long-size*))))
)

    (file-position stream 1)
    (setq str-numstr (read-byte stream))
    (setq str-numstr (change-byte-order str-numstr))
    ;; FIXME: Not sure about next line
   (file-position stream 6)
    (setq offsets (loop for i from 0 to (1- str-numstr) collect
                        (change-byte-order (read-byte stream))
)
)

    (close stream)
    offsets
)
)
И получение строки:
(defun get-new-word (stream offsets &optional (delimeter "%"))
  "Return random word (and forms) and new offsets list"
  (let* ((len (length offsets))
         (idx (random len))
         (offset (nth idx offsets))
         lines
)

    ;; Getting offsets list without element "offset"
   (setq offsets (remove offset offsets))
    
    ;; Reading word
   (file-position stream offset)
    (setq lines
          (loop for line = (read-line stream)
                collect line while (not (string= line delimeter))
)
)

    (values offsets (butlast lines))
)
)

> А вообще, еще лучше не связываться с файловым io каждый раз при получении обучающей пары.Словарь можно сконвертить в тот же ассоциативный список и загружать его в память при каждой загрузке
Точно, тогда будет работать везде, но как раз боюсь считывать файл сразу из-за возможного размера. Попробую, по крайней мере
· original author: shamaz.mazum
Оставлю здесь несколько скринов, если никто не против. Вдруг даже кто-нибудь найдет эту прогу для себя полезной)
http://img6.imageshack.us/img6/6588/shot1y.png
http://img522.imageshack.us/img522/4015/shot2r.png
http://img217.imageshack.us/img217/1600/shot3.png
· original author: allchemist
все равно не понятно зачем так сложно. если делается индекс, то его легко сделать, пройдясь один раз по всему файлу, например, с помощью peek-char.
· original author: shamaz.mazum
Хорошо, так и сделаю. Просто сначала мне понравилось, что есть готовая прога strfile, и я совсем забыл про возможные помехи, связанные с её использованием.
Спасибо за помощь.