16 Jul 2013, 02:25 Dave Thomas (338 posts) The Lists chapter had an exercise about calculating sales tax. We now have the sales information in a file of comma-separated id, ship_to, and amount values. The file looks like this: ``````id,ship_to,net_amount 123,:NC,100.00 124,:OK,35.50 125,:TX,24.00 126,:TX,44.80 127,:NC,25.00 128,:MA,10.00 129,:CA,102.00 120,:NC,50.00 `````` Write a function that reads and parses this file, and then passes the result to the sales tax function. Remember that the data should be formatted into a keyword list, and that the fields need to be the correct types (so the id field is an integer, and so on). Youâ€™ll need the library functions `File.open`, ```IO.read(file, :line)```, and `IO.stream(file)`. A Possible Solution ```defmodule SimpleCSV do def read(filename) do file = File.open!(filename) headers = read_headers(IO.read(file, :line)) Enum.map(IO.stream(file), create_one_row(headers, &1)) end defp read_headers(hdr_line) do from_csv_and_map(hdr_line, binary_to_atom(&1)) end defp create_one_row(headers, row_csv) do row = from_csv_and_map(row_csv, maybe_convert_numbers(&1)) Enum.zip(headers, row) end defp from_csv_and_map(row_csv, mapper) do row_csv |> String.strip |> String.split(%r{,\s*}) |> Enum.map(mapper) end defp maybe_convert_numbers(value) do cond do Regex.match?(%r{^\d+\$}, value) -> binary_to_integer(value) Regex.match?(%r{^\d+\.\d+\$}, value) -> binary_to_float(value) << ?: :: utf8, name :: binary >> = value -> binary_to_atom(name) true -> value end end end defmodule Tax do def orders_with_total(orders, tax_rates) do orders |> Enum.map(add_total_to(&1, tax_rates)) end def add_total_to(order = [id: _, ship_to: state, net_amount: net], tax_rates) do tax_rate = Keyword.get(tax_rates, state, 0) tax = net*tax_rate total = net+tax Keyword.put(order, :total_amount, total) end end tax_rates = [ NC: 0.075, TX: 0.08 ] orders = SimpleCSV.read("sales_data.csv") IO.inspect Tax.orders_with_total(orders, tax_rates) ``` 29 Apr 2014, 18:46 Ernie Miller (4 posts) For Elixir 0.13, this solution seems to need updates to use captures. For instance: ``````# previous: # create_one_row(headers, &1) &create_one_row(headers, &1) `````` Also, IO.stream now requires `:line` as a second parameter. ``````Enum.map(IO.stream(file, :line), &create_one_row(headers, &1)) `````` Oh, also the sigil for regexps should be changed from % to ~. You must be logged in to comment