Ведение нескольких открытых файлов для записи (Clojure) - PullRequest
3 голосов
/ 05 января 2012

Мне нужно написать функцию, которая разбивает записи на отдельные файлы в зависимости от значения поля.Например, с учетом ввода:

[
  ["Paul" "Smith" 35]
  ["Jason" "Nielsen" 39]
  ["Charles" "Brown" 22]
  ]

В итоге мы получим файл "Paul", содержащий "Paul Smith 35", файл "Jason", содержащий "Jason Nielsen 39" и т. Д.

Не знаюЯ не знаю имен заранее, поэтому мне нужно сохранить ссылки на авторов, чтобы я мог закрыть их в конце.

Лучшее, что я мог придумать, - это использование ссылки для сохранения авторов, напримерэто:

(defn write-split [records]
(let [out-dir (io/file "/tmp/test/")
      open-files (ref {})]
  (try
    (.mkdirs out-dir)
    (dorun
      (for [[fst lst age :as rec] records]
        (binding [*out* (or
                          (@open-files fst)
                          (dosync
                            (alter open-files assoc fst (io/writer (str out-dir "/" fst)))
                            (@open-files fst)))]
          (println (apply str (interpose " " rec))))))
    (finally (dorun (map #(.close %) (vals @open-files)))))))

Это работает, но чувствует себя ужасно и, что более важно, заканчивается куча, хотя у меня есть только пять выходных файлов, которые открыты в самом начале.Кажется, что-то как-то сохраняется ...

Кто-нибудь может подумать о более функциональном и Clojure-подобном решении?

РЕДАКТИРОВАТЬ: Вход большой.Потенциально гигабайты данных, отсюда важность эффективности памяти и нежелание закрывать файлы после каждой записи.

Ответы [ 4 ]

3 голосов
/ 06 января 2012
(use '[clojure.string :only (join)])

(defn write-records! [records]
  (let [writers (atom {})]
    (try 
      (doseq [[filename :as record] records]
        (let [w (or (get @writers
                         filename)
                    (get (swap! writers assoc filename (writer filename)) filename))]
          (.write w (str (join " " record) "\n"))))
      (finally (dorun (map #(.close (second %)) @writers))
               (reset! writers {})))))
2 голосов
/ 05 января 2012

Интересно, связана ли ваша проблема с нехваткой кучи с использованием привязки в for.Похоже, что для каждой записи новый код требуется для каждой записи, и, возможно, старые сохраняются.(Я могу быть совершенно неправ в этом, привязка clojure для меня является мрачным искусством).

Возможно, вы решите, чтобы ваш основной код сортировки записей помещал данные в очереди (возможно, по одному на логический файл).Затем пусть некоторые «рабочие» (может быть, функции писателя, закрывающие соответствующую привязку out ) извлекают из очередей что-то из библиотек java Executor.(Этот вопрос: «Спящий поток внутри ExecutorService (Java / Clojure)» * может дать некоторые подсказки.)

Вам все равно придется изящно обрабатывать завершение работы рабочих и закрытие файлов.как-то.(Этот другой вопрос «Агенты Clojure, потребляющие очередь» может предложить подход.)

Удачи!Необходимость сопряжения абстракции последовательностей над бесконечными данными с неизбежным императивным состоянием файловой системы не тривиальна (но, надеюсь, все еще проще в Clojure, чем в других языках).

0 голосов
/ 06 января 2012
(use '[clojure.contrib.string :only [join]])

(def vecs [["Paul" "Smith" 35]["Jason" "Nielsen" 39]["Charles" "Brown" 22]]) 

(defn write-files [v] 
  (doseq [i v]
     (spit (i 0) ; the (0 1) gets the elem in the index 0 of the vec
            (join " " i))))

(write-files vecs)

это шоуд работа.

0 голосов
/ 05 января 2012

with-open может обработать закрытие файлов для вас.

(ns sandbox.core
  (:require [clojure.java.io :as io]))

(def data [["Paul" "Smith" 35]
           ["Jason" "Nielsen" 39]
           ["Charles" "Brown" 22]])

(doseq [record data]
  (with-open [w (io/writer (first record))]
    (binding [*out* w]
      (apply println record))))

На основании ваших правок вы не хотите открывать и закрывать файлы все время по соображениям производительности,Один из подходов заключается в том, чтобы держать авторов в кэше.Следующий подход использует core.memoize для запоминания функции get-writer.После записи всех записей кэшированные авторы закрываются.

(defn write-data [data]
  (let [get-writer (memoize/memo #(io/writer % :append true))]
    (try
      (doseq [record data]
        (let [w (get-writer (first record))]
          (binding [*out* w]
            (apply println record))))
      (finally
       (dorun (map  #(.close %)
                    (vals (memoize/snapshot get-writer))))))))
...