2012-01-05 57 views
3

我需要编写一个函数,根据字段的值将记录拆分为单独的文件。例如。给定输入:维护多个打开的文件(Clojure)

[ 
    ["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的解决方案吗?

编辑:输入很大。有可能是千兆字节的数据,因此内存效率的重要性,以及每次写入后都不愿意关闭文件。

+0

记录数据如此之大,您无法将其分组在内存中?'(group-by first records)',然后在返回的地图中为每个新键打开和关闭一个文件。 – ponzao 2012-01-05 12:50:15

+0

是的,这是很大的 - 通过一个懒惰的序列进入数据的千兆字节。理想情况下, – George 2012-01-05 13:34:51

+0

是否有要求保持文件打开状态,还是需要打开有限的文件总数?大多数操作系统只允许一定数量的打开文件。 – deterb 2012-01-05 13:51:06

回答

3
(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 {}))))) 
+0

'@'符号在这里做什么?它似乎不是非常有Google功能的 – wrongusername 2012-01-06 02:58:41

+1

'@ form'意思是'(deref形式)' – 2012-01-06 08:23:10

0

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)))))))) 
2

我不知道如果你用完了堆的问题以某种方式与使用的for内结合。它看起来像你的代码需要一个新的绑定每个记录,也许旧的被保留。 (我对此可能完全错误,clojure绑定对我来说是一种黑暗的艺术)。

您可能会考虑让您的主记录排序代码将数据放入队列(可能每个逻辑文件一个)。然后让一些“工作人员”(也许编写器函数关闭合适的绑定)从队列中使用来自java执行程序库的东西。 (这个问题:"Sleeping a thread inside an ExecutorService (Java/Clojure)"可能会提供一些提示。)

您仍然必须妥善处理关闭工作人员并以某种方式关闭文件。 (这个其他问题"Clojure agents consuming from a queue"可能会提示一种方法。)

祝你好运!不得不将无序数据上的序列抽象与文件系统必然的命令式状态联系起来并不是微不足道的(但希望在Clojure中仍然比在其他语言中简单)。

0
(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) 

这个工作。