CSV spreadsheet files are suitable for storing tabular data in a relatively portable way.

The CSV format is flexible but somewhat ill-defined.

For present purposes, authors may assume that the data fields contain no commas, backslashes, or quotation marks.



Task

Read a CSV file, change some values and save the changes back to a file.

For this task we will use the following CSV file:

C1,C2,C3,C4,C5
1,5,9,13,17
2,6,10,14,18
3,7,11,15,19
4,8,12,16,20

Suggestions


 Show how to add a column, headed 'SUM', of the sums of the rows.
 If possible, illustrate the use of built-in or standard functions, methods, or libraries, that handle generic CSV files.



 
defmodule Csv do
  defstruct header: "", data: "", separator: ","
 
  def from_file(path) do
    [header | data] = path
    |> File.stream!
    |> Enum.to_list
    |> Enum.map(&String.trim/1)
 
    %Csv{ header: header, data: data }
  end
 
  def sums_of_rows(csv) do
    Enum.map(csv.data, fn (row) -> sum_of_row(row, csv.separator) end)
  end
 
  def sum_of_row(row, separator) do
    row
    |> String.split(separator)
    |> Enum.map(&String.to_integer/1)
    |> Enum.sum
    |> to_string
  end
 
  def append_column(csv, column_header, column_data) do
    header = append_to_row(csv.header, column_header, csv.separator)
 
    data = [csv.data, column_data]
    |> List.zip
    |> Enum.map(fn ({ row, value }) ->
      append_to_row(row, value, csv.separator)
    end)
 
    %Csv{ header: header, data: data }
  end
 
  def append_to_row(row, value, separator) do
    row <> separator <> value
  end
 
  def to_file(csv, path) do
    body = Enum.join([csv.header | csv.data], "\n")
 
    File.write(path, body)
  end
end
 
csv = Csv.from_file("in.csv")
csv
|> Csv.append_column("SUM", Csv.sums_of_rows(csv))
|> Csv.to_file("out.csv")
 