small medium large xlarge

Dave_gnome_head_isolated_pragsmall
15 Jul 2013, 03:42
Dave Thomas (366 posts)
  • (Harder) Sometimes the first line of a CSV file is a list of the column names. Update your code to support this, and return the values in each row as a keyword list using the column names as the keys.

    csv = %c"""
    Item,Qty,Price
    Teddy bear,4,34.95
    Milk,1,2.99
    Battery,6,8.00
    """
    

    Would generate:

    [
      [Item: "Teddy bear", Qty: 4, Price: 34.95],
      [Item: "Milk", Qty: 1, Price: 2.99],
      [Item: "Battery", Qty: 6, Price: 8.00]
    ]
    
Generic-user-small
27 Dec 2014, 14:20
Tim Rozmajzl (3 posts)
defmodule MySigil do

  def sigil_v(string, _opt) do
    [ header | rows ] = String.split(string, "\n", trim: true)
                     |> Enum.map &String.split(&1, ",", trim: true)
    header = header  |> Enum.map &String.to_atom/1

    for row <- rows do
      List.zip [ header, Enum.map(row, &_convert/1) ]
    end
  end

  def _convert(element) do
     case Float.parse(element) do
       { x, "" } -> x
       _       -> 
         case Integer.parse(element) do
           { x, "" } -> x
           _ -> element
         end
     end
  end

end
Generic-user-small
29 Jan 2015, 17:43
Pierre Sugar (57 posts)
defmodule CsvSigil do

  def sigil_v(lines, _opts) do
    [head | tail] = lines
                    |> String.rstrip
                    |> String.split("\n")
    header = String.split(head, ",")
    Enum.reduce(tail, [], fn l, acc -> acc ++ [assign(to_csv(l), header)] end)
  end

  def assign([value | values], [first | rest]) do
    [{String.to_atom(first), value} | assign(values, rest)]
  end
  def assign([],[]), do: []

  def to_csv(values) do
    values 
    |> String.split(",") 
    |> Enum.reduce([], fn v, acc -> [parse(v) | acc] end)
    |> Enum.reverse
  end

  def parse(string), do: _parse(Float.parse(string), string)
  defp _parse({number, rest}, _string) when rest == "", do: number
  defp _parse(             _,  string)                , do: string
end

defmodule Example do
  import CsvSigil

  def lines do
    ~v"""
    Item,Qty,Price
    Teddy bear,4,34.95
    Milk,1,2.99
    Battery,6,8.00
    """
  end
end

Will print

iex(160)> Example.lines                                                         
[[Item: "Teddy bear", Qty: 4.0, Price: 34.95],                                  
 [Item: "Milk", Qty: 1.0, Price: 2.99], 
 [Item: "Battery", Qty: 6.0, Price: 8.0]]
Generic-user-small
07 Dec 2015, 16:36
Aaron K. (4 posts)
defmodule CSVSigil do

  def sigil_v(lines, []) do
    lines
    |> String.rstrip
    |> split_newlines
    |> split_commas
    |> format_rows
  end

  def sigil_v(lines, 'h') do
    [ headers | rows ] = lines |> split_newlines |> split_commas
    headers = Enum.map(headers, &String.to_atom/1)

    Enum.map(
      rows,
      fn(row) ->
        List.zip([headers, format_row(row)])
      end
    )
  end

  defp split_newlines(lines) do
    String.split(lines, "\n", trim: true)
  end

  defp split_commas(lines) do
    Enum.map(
      lines,
      fn(line) ->
        String.split(line, ",", trim: true)
      end
    )
  end

  def format_rows(rows) do
    Enum.map(
      rows,
      fn(row) ->
        format_row(row)
      end
    )
  end

  def format_row(row) do
    Enum.map(
      row,
      fn(column) ->
        format_column(column)
      end
    )
  end

  def format_column(column) do
    case [ Integer.parse(column), Float.parse(column) ] do
      [ { integer, rational }, _ ] when rational == "" -> integer
      [ _, { float, _ } ] -> float
      _ -> column
    end
  end

  defmacro __using__(_opts) do
    quote do
      import unquote(__MODULE__), only: [ sigil_v: 2 ]
    end
  end
end

defmodule Example do

  use CSVSigil

  def to_table_list_with_headers do
    ~v"""
    Item,Quantity,Price
    Teddy Bear,4,34.95
    Milk,1,2.99
    Battery,6,8.00
    """h
  end
end
iex(1)> Example.to_table_list_with_headers
[[Item: "Teddy Bear", Quantity: 4, Price: 34.95],
 [Item: "Milk", Quantity: 1, Price: 2.99],
 [Item: "Battery", Quantity: 6, Price: 8.0]]

The only bit I wasn’t able to figure is forcing a float value to have a specific precision. In thee case of the “Battery” item, it’s Price should be 8.00, however my solution produces 8.0

Generic-user-small
04 Apr 2016, 13:24
Stefan Houtzager (8 posts)

The exact output asked for:

[ [Item: “Teddy bear”, Qty: 4, Price: 34.95], [Item: “Milk”, Qty: 1, Price: 2.99], [Item: “Battery”, Qty: 6, Price: 8.00] ]

could be reached by outputting to a file. For now I just produced the output without linebreaks and preceding spaces on new lines with IO.inspect.

defmodule LineSigil do
  def sigil_v(lines, _opts) do
    [header | rows ] =  lines
      |> String.rstrip
      |> String.split("\n")
      |> Enum.map(fn x -> x |> String.split(",") end)
    header = header
             |> Enum.map(&String.to_atom/1)
    rows
    |> Enum.map(fn col -> processCollection(col) end)
    |> Enum.map(fn row -> List.zip([header,row]) end)
  end

  defp processCollection(col), do:
    col |> Enum.map(fn element ->
                      case Integer.parse(element) do
                        {integer, ""} -> integer
                        _             -> case Float.parse(element) do
                                           {float, _} -> Float.to_string(float, [decimals: 2])
                                            _         -> element
                                      end
                      end
                    end)

end

defmodule Example do
  import LineSigil
  def lines do
    ~v"""
    Item,Qty,Price
    Teddy bear,4,34.95
    Milk,1,2.99
    Battery,6,8.00
    """
  end
end

IO.inspect Example.lines
#This produces
#[[Item: "Teddy bear", Qty: 4, Price: "34.95"],
# [Item: "Milk", Qty: 1, Price: "2.99"],
# [Item: "Battery", Qty: 6, Price: "8.00"]]

You must be logged in to comment