Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Output tabular data with IO.ANSI

Tags:

elixir

I would like to render a 2-dimensional list to a nice tabular output, using an ANSI escape sequences to control the formatting.

So given this data:

data = [
  [ 100, 20, 30 ],
  [ 20, 10, 20 ],
  [ 50, 400, 20 ]
]

I would like to output something like this:

100  20   30
20   10   20
50   400  20

Many Thanks

like image 395
b73 Avatar asked Dec 25 '22 18:12

b73


1 Answers

In the "Programming Elixir" book by Dave Thomas there is a similar to what you need exercise todo:

write the code to format the data into columns, like the sample output at the start 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 to this exercise where you can find and pick what suites you. You will need to modify your code though to make it work with your input data structure (an array of arrays, so a matrix) but that should be easy. If you have any trouble doing, this just ask.

BTW here is my solution 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 this as inspiration not a direct solution to your problem. This may also be much more that what your needs are, but being able quickly format some tabular data in a generic way and print them in your terminal can be very handy in the future for you.

like image 55
Szymon Jeż Avatar answered Jan 03 '23 05:01

Szymon Jeż