使用Elixir将文件解析为Map:Enum.zip函数使用是否正确?
问题:解析文本到Elixir Map仅得到3个条目而非全部
尝试将外部查询结果的文本文件解析为Elixir的Map,实际数据有30余条,但最终仅得到3个条目。通过inspect查看确认所有数据都被解析过,但结果不符合预期,怀疑是否错误使用了Enum.zip函数。
模块代码
defmodule Statistics do def read(path) do case File.read(path) do {:ok, body} -> parse_body(body) {:error, reason} -> IO.puts(~s(could not open file "#{path}"\n)) IO.puts(~s("#{:file.format_error(reason)}\n")) end end def parse_lines(lines, keys) do Enum.reduce(lines, %{}, fn line, built -> [name | fields] = String.split(line, "\t") # IO.inspect(fields) if Enum.count(keys) == Enum.count(fields) do line_data = Enum.zip(keys, fields) |> Enum.into(%{}) Map.merge(built, %{name => line_data}) else built end end) end def parse_body(body) do [header | lines] = String.split(body, ~r(\r\n|\r|\n)) keys = tl(String.split(header, "\t")) parse_lines(lines, keys) end end
文本文件内容
Abandon_agent Abandon_system answered_greater_than_20 answered_less_than_20 answered_less_than_15 answered_less_than_10 answered_less_than_5 date 0 0 0 0 0 0 0 2022-09-29 0 0 0 0 0 0 0 2022-10-01 0 0 0 0 0 0 0 2022-10-02 0 0 0 0 4 24 9 2022-10-03 0 0 2 0 6 22 23 2022-10-04 2 0 0 0 7 16 21 2022-10-05 1 0 1 0 8 12 35 2022-10-06 0 0 0 2 8 9 29 2022-10-07 0 0 0 0 0 0 0 2022-10-08 0 0 0 0 0 0 0 2022-10-09 0 0 2 3 3 18 12 2022-10-10 0 0 1 2 5 16 6 2022-10-11 0 0 0 2 6 24 19 2022-10-12 0 0 1 2 3 20 29 2022-10-13 1 0 1 2 2 11 10 2022-10-14 0 0 0 0 0 0 0 2022-10-15 0 0 0 0 0 0 0 2022-10-16 0 0 1 1 6 17 11 2022-10-17 0 0 1 1 4 16 11 2022-10-18 0 0 0 1 7 18 12 2022-10-19 0 0 1 1 6 21 9 2022-10-20 1 0 0 3 1 20 17 2022-10-21 0 0 0 0 0 0 0 2022-10-22 0 0 0 0 0 0 0 2022-10-23 0 0 1 1 9 37 15 2022-10-24 0 0 0 1 4 21 14 2022-10-25 1 0 1 2 0 21 12 2022-10-26 0 0 3 2 7 17 13 2022-10-27 0 0 1 0 1 9 27 2022-10-28 0 0 0 0 0 0 0 2022-10-29 0 0 0 0 0 0 0 2022-10-30 0 0 0 0 4 21 8 2022-10-31
实际输出结果
%{ "0" => %{ "Abandon_system" => "0", "answered_greater_than_20" => "0", "answered_less_than_10" => "21", "answered_less_than_15" => "4", "answered_less_than_20" => "0", "answered_less_than_5" => "8", "date" => "2022-10-31" }, "1" => %{ "Abandon_system" => "0", "answered_greater_than_20" => "1", "answered_less_than_10" => "21", "answered_less_than_15" => "0", "answered_less_than_20" => "2", "answered_less_than_5" => "12", "date" => "2022-10-26" }, "2" => %{ "Abandon_system" => "0", "answered_greater_than_20" => "0", "answered_less_than_10" => "16", "answered_less_than_15" => "7", "answered_less_than_20" => "0", "answered_less_than_5" => "21", "date" => "2022-10-05" } }
问题原因
和Enum.zip无关,核心问题在于你用了文本第一列(Abandon_agent)的值作为Map的键,而该列的值只有0、1、2三种。每次执行Map.merge(built, %{name => line_data})时,相同键对应的旧数据会被新数据覆盖,最终每个键只保留最后一次出现的那条记录。
解决方案
要保留所有数据,需要更换唯一的键,或者将所有条目存储为列表:
方案1:用date作为唯一键(日期不重复)
修改parse_lines函数:
def parse_lines(lines, keys) do Enum.reduce(lines, %{}, fn line, built -> [_abandon_agent | fields] = String.split(line, "\t") date = List.last(fields) # 提取日期作为键 if Enum.count(keys) == Enum.count(fields) do line_data = Enum.zip(keys, fields) |> Enum.into(%{}) Map.merge(built, %{date => line_data}) else built end end) end
方案2:返回所有条目的列表
如果不需要Map结构,直接返回解析后的列表:
def parse_lines(lines, keys) do Enum.flat_map(lines, fn line -> [_abandon_agent | fields] = String.split(line, "\t") if Enum.count(keys) == Enum.count(fields) do [Enum.zip(keys, fields) |> Enum.into(%{})] else [] end end) end
方案3:使用复合键(比如Abandon_agent + date)
如果需要保留原列信息,用组合键避免重复:
def parse_lines(lines, keys) do Enum.reduce(lines, %{}, fn line, built -> [abandon_agent | fields] = String.split(line, "\t") date = List.last(fields) key = "#{abandon_agent}_#{date}" # 生成复合键 if Enum.count(keys) == Enum.count(fields) do line_data = Enum.zip(keys, fields) |> Enum.into(%{}) Map.merge(built, %{key => line_data}) else built end end) end
内容的提问来源于stack exchange,提问作者keepTrackOfYourStack
相关产品推荐
相关产品推荐

