我需要解析大量的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
,但无法弄清楚如何跳过第一个元素。结构可以存储在累加器中。
答案 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