如何在 Haskell 中重复读取大数据文件的乱序行?

How can I repeatedly read in shuffled lines of a large data file in Haskell?

我有一个 60k 行的数据文件,其中每行都有 ~1k 逗号分隔的整数(我想立即将其转换为双精度数)。

我想遍历 32 行的随机 "batches" 序列,其中一个批次是所有行的随机子集,并且 none 个批次共享公共行。由于每批有60k行,32行,所以应该有1875批。

如有必要,我愿意改变一些事情,但我希望它们采用延迟评估的列表(批次)的形式。需要这个的代码是 foldM,我在这里使用它:

resulting_struct <- foldM fold_fn my_struct batch_list

以便它在当前累加器 my_struct 的结果和 batch_list 的下一个元素上重复调用 fold_fn

我很困惑。当我不需要洗牌时很容易;我简单地读入它们并将它们分块,它们被懒惰地评估,所以我没有问题。现在我完全被卡住了,觉得我一定是错过了一些简单的东西。

我试过以下方法:

  1. 将文件读入行列表并天真地打乱输入。这是行不通的,因为 readFile 是延迟求值的,但它需要将整个文件读入内存以随机洗牌,它很快就会耗尽我所有的 ~8 GB 内存。

  2. 获取文件的长度,然后创建一个从 0 到 60k 的混洗 索引 的批次列表,这些批次对应于将选择形成批次。然后,当我想实际获取数据批次时,我会这样做:

ind_batches <- get_shuffled_ind_batches_from_file fname
batch_list <- mapM (get_data_batch_from_ind_batch fname) ind_batches

其中:

get_shuffled_ind_batches_from_file :: String -> IO [[Int]]
get_shuffled_ind_batches_from_file fname = do
  contents <- get_contents_from_file fname -- uses readFile, returns [[Double]]
  let n_samps = length contents
      ind = [0..(n_samps-1)]
  shuffled_indices <- shuffle_list ind
  let shuffled_ind_chunks = take 1800 $ chunksOf 32 shuffled_indices
  return shuffled_ind_chunks

get_data_batch_from_ind_batch :: String -> [Int] -> IO [[Double]]
get_data_batch_from_ind_batch fname ind_chunk = do
  contents <- get_contents_from_file fname
  let data_batch = get_elems_at_indices contents ind_chunk
  return data_batch

shuffle_list :: [a] -> IO [a]
shuffle_list xs = do
        ar <- newArray n xs
        forM [1..n] $ \i -> do
            j <- randomRIO (i,n)
            vi <- readArray ar i
            vj <- readArray ar j
            writeArray ar j vi
            return vj
  where
    n = length xs
    newArray :: Int -> [a] -> IO (IOArray Int a)
    newArray n xs =  newListArray (1,n) xs

get_elems_at_indices :: [a] -> [Int] -> [a]
get_elems_at_indices my_list ind_list = (map . (!!)) my_list ind_list

然而,似乎 mapM 立即评估,然后尝试重复读取文件内容(我想,RAM 无论如何都会爆炸)。

  1. 更多的搜索告诉我,我可以尝试使用 unsafeInterleaveIO 来让它懒惰地评估一个动作,所以我试着像这样坚持下去:
get_data_batch_from_ind_batch :: String -> [Int] -> IO [[Double]]
get_data_batch_from_ind_batch fname ind_chunk = unsafeInterleaveIO $ do
  contents <- get_contents_from_file fname
  let data_batch = get_elems_at_indices contents ind_chunk
  return data_batch

但是没有运气,和上面一样的问题。

我觉得我一直在用头撞墙,一定是漏掉了一些非常简单的东西。有人建议改用流或管道,但是当我查看它们的文档时,我并不清楚如何使用它们来解决这个问题。

我怎样才能读入一个大数据文件并随机播放它,而不用尽我所有的内存?

hGetContents 会延迟 return 文件的内容,但是如果您对结果做很多事情,您将立即实现整个文件。我建议读取文件一次,然后扫描它以查找换行符,这样您就可以建立一个索引,说明哪个块从哪个字节偏移量开始。该索引将非常小,因此您可以轻松地对其进行洗牌。然后你可以遍历索引,每次打开文件并只读取它的一个定义的子范围,并且只解析那个块。