CSV到Elixir中的地图流

时间:2016-05-04 22:29:32

标签: csv elixir

我需要解析大量的csv数据,其中文件的第一行是标题。库:csv已经为我提供了一个列表流,我需要从第一行推断出结构但忽略它,然后生成具有给定结构的地图流。

我喜欢这个:

data.csv

a,b
1,2
3,4
...

CSV.stream_map(文件名)输出

{a: 1, b: 2} #1
{a: 3, b: 4} #2
...

我正在调查Stream.transform,但无法弄清楚如何跳过第一个元素。结构可以存储在累加器中。

3 个答案:

答案 0 :(得分:8)

如果您将headers: true作为第二个参数传递给CSV.decode/2(如docs中所述),它会自动将第一行用作键名并返回一个地图对于以下所有行。

iex(1)> CSV.decode(File.stream!("data.csv"), headers: true) |> Enum.to_list
[%{"a" => "1", "b" => "2"}, %{"a" => "3", "b" => "4"}]

data.csv包含:

a,b
1,2
3,4

答案 1 :(得分:3)

虽然csv模块已经发现了,但我也找到了一种方法来实现这一点。事实证明,如果您在[]回调中发回一个空列表Stream.transform,则不会将任何元素推送到流中:

def map_stream(enum) do
    enum
    |> Stream.transform(:first, &structure_from_header/2)
end

#The accumulator starts as :first, the its the structure of the csv
#that is the first line
def structure_from_header(line, :first),
    do: { [ ], line } #<=================== Here is the trick

def structure_from_header(line, structure) do
    map = 
      structure
      |> Enum.zip(line)
      |> Enum.into(%{})

{ [ map ], structure }
end

答案 2 :(得分:1)

我认为有两种选择。在这里,您可以设置块大小,以便不将整个文件加载到内存中,并且可以处理它的集合。如果需要解析数据,请不要使用流解决方案。在这两个中我都展示了如何跳过标题。至于创建地图结构,您可以查看structs,然后利用结构为地图集创建结构。如果你有很多列我建议使用MapSet而不是地图。

def stream_parse(file_path, chunk_size) do
  file_path
    |> File.stream!
    |> Stream.drop(1)
    |> Stream.map(&String.split(&1, ","))
    |> Stream.chunk(chunk_size, chunk_size, [])
    |> Stream.map(&MapSet.new(&1))
end

def flow_parse(file_path, chunk_size) do
  file_path
    |> File.stream!(read_ahead: chunk_size)
    |> Stream.drop(1)
    |> Flow.from_enumerable
    |> Flow.map(&String.split(&1, ","))
    |> Flow.partition
    |> Flow.map(&MapSet.new(&1)
end