Outputting Tabular Data with IO.ANSI
Here's a more complex solution that works with arbitrarily wide values:
defmodule Table do
def format(rows, opts \\ []) do
padding = Keyword.get(opts, :padding, 1)
rows = stringify(rows)
widths = rows |> transpose |> column_widths
rows |> pad_cells(widths, padding) |> join_rows
end
defp pad_cells(rows, widths, padding) do
Enum.map rows, fn row ->
for {val, width} <- Enum.zip(row, widths) do
String.ljust(val, width + padding)
end
end
end
def join_rows(rows) do
rows |> Enum.map(&Enum.join/1) |> Enum.join("\n")
end
defp stringify(rows) do
Enum.map rows, fn row ->
Enum.map(row, &to_string/1)
end
end
defp column_widths(columns) do
Enum.map columns, fn column ->
column |> Enum.map(&String.length/1) |> Enum.max
end
end
# http://stackoverflow.com/questions/23705074
defp transpose([[]|_]), do: []
defp transpose(rows) do
[Enum.map(rows, &hd/1) | transpose(Enum.map(rows, &tl/1))]
end
end
Use it like this:
Table.format(data, padding: 2) |> IO.puts
Prints:
100 20 30
20 10 20
50 400 20
source to share
The "Elixir Programming" book Dave Thomas is what you need to do todo:
write code to format the data into columns, for example the sample output at the beginning of the chapter:
# | Created at | Title
-----+----------------------+--------------------------------------------------
2722 | 2014-08-27T04:33:39Z | Added first draft of File.mv for moving files aro
2728 | 2014-08-29T14:28:30Z | Should elixir (and other applications) have stick
-----+----------------------+--------------------------------------------------
and there is a place where readers can post their solutions for this exercise , where you can find and choose which numbers you have. You will need to modify your code to make it work with your input data structure (an array of arrays, and therefore a matrix), but that should be easy. If you have problems, just ask.
By the way, here is my solution that I wrote while reading the book:
defmodule Issues.TableFormatter do
import Enum, only: [map: 2, max: 1, zip: 2, join: 2]
@header ~w(number created_at title)
@header_column_separator "-+-"
# TODO: Refactor; try to find more uses for stdlib functions
def print_table(rows, header \\ @header) do
table = rows |> to_table(header)
header = header |> map(&String.Chars.to_string/1) # the headers needs now to be a string
columns_widths = [header | table] |> columns_widths
hr = for _ <- 1..(length(header)), do: "-"
hr |> print_row(columns_widths, @header_column_separator)
header |> print_row(columns_widths)
hr |> print_row(columns_widths, @header_column_separator)
table |> map &(print_row(&1, columns_widths))
hr |> print_row(columns_widths, @header_column_separator)
end
def to_table(list_of_dicts, header) do
list_of_dicts
|> select_keys(header)
|> map(fn (dict) ->
dict
|> Dict.values
|> map(&String.Chars.to_string/1)
end)
end
def columns_widths(table) do
table
|> Matrix.transpose
|> map(fn (cell) ->
cell
|> map(&String.length/1)
|> max
end)
end
def select_keys(dict, keys) do
for entry <- dict do
{dict1, _} = Dict.split(entry, keys)
dict1
end
end
def print_row(row, column_widths, separator \\ " | ") do
padding = separator |> String.to_char_list |> List.first # Hack
IO.puts row
|> zip(column_widths)
|> map(fn ({ cell, column_width }) ->
String.ljust(cell, column_width, padding)
end)
|> join(separator)
end
end
Treat all of this as inspiration, not a direct solution to your problem. It may also be much more than what your needs are, but being able to quickly format some tabular data in general and print it out in your terminal might be very handy for you in the future.
source to share