2015-06-10 2 views
3

Я хотел бы сделать 2-мерный список хорошим выводным табличным выражением, используя escape-последовательности ANSI для управления форматированием.Вывод табличных данных с IO.ANSI

Поэтому, учитывая эти данные:

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

Я хотел бы выход что-то вроде этого:

100 20 30 
20 10 20 
50 400 20 

Большое спасибо

ответ

3

Вот более сложное решение, которое работает с произвольно широкими значениями:

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 

использовать его как это:

Table.format(data, padding: 2) |> IO.puts 

Печатается:

100 20 30 
20 10 20 
50 400 20 
+0

Спасибо, Патрик, это действительно приятное решение :) – b73

+0

В случае, если кому-то интересно, я переработал этот код на https://gist.github.com/ivan/dc26e349de6f1693c1c77355858564, чтобы 1) вернуть iodata 2) not pad последний столбец 3) не разбивает VM на 0 строк –

3

Вы можете достичь расстояния с символа табуляции, если каждый элемент составляет менее 7 символов:

iex> Enum.each(data, fn (x) -> Enum.join(x, "\t") |> IO.puts end) 
100  20  30 
20  10  20 
50  400  20 
:ok 
+0

Благодаря Gazler, я люблю простоту этого варианта. Хотя в моей ситуации каждый элемент может быть длиннее 7 символов. Еще раз спасибо – b73

3

В "Programming Elixir" book by Dave Thomas есть аналогичный к чему вам нужно заниматься:

написать код для форматирования данных в столбцы, как на выходе образца в начале главы:

# | 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 
-----+----------------------+-------------------------------------------------- 

и есть a place where readers can post their solutions to this exercise, где вы можете найти и выбрать то, что костюмы вы. Вам нужно будет модифицировать свой код, чтобы заставить его работать с вашей структурой входных данных (массив массивов, а значит, и с матрицей), но это должно быть легко. Если у вас есть проблемы, просто спросите.

BTW вот мое решение, которое я написал, прочитав книгу:

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 

Лечить все это как вдохновение не прямое решение вашей проблемы. Это также может быть намного больше того, что ваши потребности, но возможность быстро форматировать некоторые табличные данные в общем виде и печатать их в вашем терминале может быть очень удобна в будущем для вас.

+0

Спасибо Simon Jez, в этом потоке есть некоторые хорошие примеры. Вы оказали большое вдохновение - спасибо. – b73

0

Существует библиотека printf для Elixir, которая делает такие вещи проще, если вы не знакомы с формированием строки Erlang.

https://github.com/parroty/exprintf

Смежные вопросы