Proper place for this contribution is perhaps somewhere else but…
Background
Often one is ‘combining’ data from several different objects in many steps. I used to stick to the Relation Algebra pattern with Selection, Projection, Product and Set Operation on Tuples in order to manage complexity.
Quite easy operations to implement in crystal. I do use NamedTuple as dataset. At least for temporary result. But NamedTuple are hard to read (during verification). Or was!! Until now
Here is a test source
# TESTING
# Some NamedTuple (as an array First Normal Form 1NF)
#
data_as_array = [{row: 0, kind: "NARROW", from_name: "TLSapi", connection_line_nr: 0, to_name: "ClientOrServer", to_col_x: 0},
{row: 0, kind: "NARROW", from_name: "TLSapi", connection_line_nr: 1, to_name: "FeMaleOrMaleConnection", to_col_x: 1},
{row: 0, kind: "NARROW", from_name: "Point", connection_line_nr: 2, to_name: "Inf", to_col_x: 2},
{row: 0, kind: "NARROW", from_name: "HKDF", connection_line_nr: 3, to_name: "HMAC", to_col_x: 3},
{row: 1, kind: "FAR", from_name: "KeyPair", connection_line_nr: 4, to_name: "Curve", to_col_x: 0}]
# Print it
puts pretty_named_tuples(data_as_array, "data_as_array")
#
# Make a Hash(key,value)
#
data_as_hash = data_as_array.group_by { |r| r[:row] }
# Print it (here just the first 3 lines only)
puts pretty_named_tuples(data_as_hash.values.flatten[0, 3], "data_as_hash")
# Or with less data and for one certain key
#
less_data = data_as_array
.map { |row| {from_name: row[:from_name], to_name: row[:to_name]} }
.group_by { |r| r[:from_name] }
#
puts pretty_named_tuples(less_data["TLSapi"], "less_data")
and the result
data_as_array
=============
| row: 0 | kind: NARROW | from_name: TLSapi | connection_line_nr: 0 | to_name: ClientOrServer | to_col_x: 0 |
| row: 0 | kind: NARROW | from_name: TLSapi | connection_line_nr: 1 | to_name: FeMaleOrMaleConnection | to_col_x: 1 |
| row: 0 | kind: NARROW | from_name: Point | connection_line_nr: 2 | to_name: Inf | to_col_x: 2 |
| row: 0 | kind: NARROW | from_name: HKDF | connection_line_nr: 3 | to_name: HMAC | to_col_x: 3 |
| row: 1 | kind: FAR | from_name: KeyPair | connection_line_nr: 4 | to_name: Curve | to_col_x: 0 |
data_as_hash
============
| row: 0 | kind: NARROW | from_name: TLSapi | connection_line_nr: 0 | to_name: ClientOrServer | to_col_x: 0 |
| row: 0 | kind: NARROW | from_name: TLSapi | connection_line_nr: 1 | to_name: FeMaleOrMaleConnection | to_col_x: 1 |
| row: 0 | kind: NARROW | from_name: Point | connection_line_nr: 2 | to_name: Inf | to_col_x: 2 |
less_data
=========
| from_name: TLSapi | to_name: ClientOrServer |
| from_name: TLSapi | to_name: FeMaleOrMaleConnection |
The source itself (like 40 rows only)
# Usage here is 'crystal run pretty_named_tuples.cr'
# ------------------------------------------------------------
def self.pretty_named_tuples(the_named_tuple_rows, title)
ret = IO::Memory.new
if the_named_tuple_rows.size == 0
return ret << "#{title} (empty)"
end
total_widths_cols = Array(Int32).new
col_names = Array(String).new
# Scan all rows! to determine col widths
the_named_tuple_rows
.each_with_index { |arow, col_nr|
widths_in_row = arow
.map { |acol|
if col_nr == 0
col_names << acol.to_s # Pick col names from first row
end
acol.to_s.size + ((arow[acol].to_s).size) + 1
}
if total_widths_cols.size == 0
total_widths_cols = widths_in_row # First column in first row!
end
total_widths_cols.each_with_index { |a_col_width, col_index|
total_widths_cols[col_index] = [total_widths_cols[col_index], widths_in_row[col_index]].max
}
}
return ret << title << "\n" << title.size.times.map { |i| "=" }.join << "\n" <<
the_named_tuple_rows.map { |arow|
col_names.map_with_index { |col_name, col_nr|
col_content = arow[col_names[col_nr]]
if col_nr == 0
left_margin = "|"
else
left_margin = ""
end
col_name_s = col_name + ":"
col_content_s = col_content.to_s
spc_cnt : Int32 = total_widths_cols[col_nr] - col_name_s.size - col_content_s.size
spacing = ((1..spc_cnt).map { |c| " " }).join
left_margin + " " + col_name_s + spacing + " " + col_content_s + " |"
}.join # join columns into a row
}.join("\n")
end
Combine test source and method source into the_thing.cr
And use the very handsome crystal run the_thing.cr to understand how to use your own data perhaps