使用 IO.ANSI 输出表格数据
Output tabular data with IO.ANSI
我想将二维列表呈现为漂亮的表格输出,使用 ANSI 转义序列来控制格式。
鉴于此数据:
data = [
[ 100, 20, 30 ],
[ 20, 10, 20 ],
[ 50, 400, 20 ]
]
我想输出这样的东西:
100 20 30
20 10 20
50 400 20
非常感谢
如果每个项目少于 7 个字符,您可以使用制表符实现间距:
iex> Enum.each(data, fn (x) -> Enum.join(x, "\t") |> IO.puts end)
100 20 30
20 10 20
50 400 20
:ok
这里有一个更精细的解决方案,适用于任意宽的值:
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
#
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
在"Programming Elixir" book by Dave Thomas中有一个类似于你需要做的练习:
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
-----+----------------------+--------------------------------------------------
还有 a place where readers can post their solutions to this exercise,您可以在其中找到并选择适合您的内容。尽管您需要修改代码以使其与您的输入数据结构(数组数组,矩阵)一起使用,但这应该很容易。如果你做的有什么困难,这只是问。
顺便说一句,这是我在看书时写的解决方案:
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
将所有这些视为灵感,而不是直接解决您的问题。这也可能远远超出您的需求,但是能够以通用方式快速格式化一些表格数据并在您的终端中打印它们在将来对您来说非常方便。
Elixir 有一个 printf 库,如果你
不熟悉 Erlang 字符串格式。
我想将二维列表呈现为漂亮的表格输出,使用 ANSI 转义序列来控制格式。
鉴于此数据:
data = [
[ 100, 20, 30 ],
[ 20, 10, 20 ],
[ 50, 400, 20 ]
]
我想输出这样的东西:
100 20 30
20 10 20
50 400 20
非常感谢
如果每个项目少于 7 个字符,您可以使用制表符实现间距:
iex> Enum.each(data, fn (x) -> Enum.join(x, "\t") |> IO.puts end)
100 20 30
20 10 20
50 400 20
:ok
这里有一个更精细的解决方案,适用于任意宽的值:
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
#
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
在"Programming Elixir" book by Dave Thomas中有一个类似于你需要做的练习:
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
-----+----------------------+--------------------------------------------------
还有 a place where readers can post their solutions to this exercise,您可以在其中找到并选择适合您的内容。尽管您需要修改代码以使其与您的输入数据结构(数组数组,矩阵)一起使用,但这应该很容易。如果你做的有什么困难,这只是问。
顺便说一句,这是我在看书时写的解决方案:
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
将所有这些视为灵感,而不是直接解决您的问题。这也可能远远超出您的需求,但是能够以通用方式快速格式化一些表格数据并在您的终端中打印它们在将来对您来说非常方便。
Elixir 有一个 printf 库,如果你 不熟悉 Erlang 字符串格式。