///|
/// Inner join two tables by key columns.
pub fn table_inner_join(
left : CsvTable,
right : CsvTable,
left_key : String,
right_key : String,
) -> CsvTable {
table_join_impl(left, right, left_key, right_key, false)
}
///|
/// Left join two tables by key columns, preserving every row from the left table.
pub fn table_left_join(
left : CsvTable,
right : CsvTable,
left_key : String,
right_key : String,
) -> CsvTable {
table_join_impl(left, right, left_key, right_key, true)
}
///|
/// Append rows from `bottom` to `top` when both tables share exactly the same headers.
pub fn table_append(table : CsvTable, bottom : CsvTable) -> CsvTable {
if !join_headers_equal(table.headers, bottom.headers) {
return table
}
let rows : Array[Array[String]] = Array::new()
for row in table.rows {
rows.push(row)
}
for row in bottom.rows {
rows.push(row)
}
{ headers: table.headers, rows }
}
///|
/// Union two tables by column name, adding missing cells as empty strings.
pub fn table_union_by_name(left : CsvTable, right : CsvTable) -> CsvTable {
let headers = left.headers.copy()
for header in right.headers {
if join_column_index(headers, header) is None {
headers.push(header)
}
}
let rows : Array[Array[String]] = Array::new()
for row in left.rows {
rows.push(join_align_row(row, left.headers, headers))
}
for row in right.rows {
rows.push(join_align_row(row, right.headers, headers))
}
{ headers, rows }
}
///|
/// Transpose a table. The first output column contains original header names.
pub fn table_transpose(table : CsvTable) -> CsvTable {
let headers : Array[String] = Array::new()
headers.push("column")
for i in 0.. CsvTable {
match
(
join_column_index(left.headers, left_key),
join_column_index(right.headers, right_key),
) {
(Some(left_index), Some(right_index)) => {
let right_keys : Array[String] = Array::new()
for row in right.rows {
let key = join_cell_at(row, right_index)
if !join_contains(right_keys, key) {
right_keys.push(key)
}
}
let rows : Array[Array[String]] = Array::new()
for row in left.rows {
if !join_contains(right_keys, join_cell_at(row, left_index)) {
rows.push(row)
}
}
{ headers: left.headers, rows }
}
_ => { headers: left.headers, rows: [] }
}
}
///|
/// Return a table with columns reordered according to `columns`, then remaining columns.
pub fn table_reorder_columns(
table : CsvTable,
columns : Array[String],
) -> CsvTable {
let ordered : Array[String] = Array::new()
for column in columns {
if join_column_index(table.headers, column) is Some(_) &&
join_column_index(ordered, column) is None {
ordered.push(column)
}
}
for header in table.headers {
if join_column_index(ordered, header) is None {
ordered.push(header)
}
}
let rows : Array[Array[String]] = Array::new()
for row in table.rows {
rows.push(join_align_row(row, table.headers, ordered))
}
{ headers: ordered, rows }
}
///|
fn table_join_impl(
left : CsvTable,
right : CsvTable,
left_key : String,
right_key : String,
keep_unmatched_left : Bool,
) -> CsvTable {
match
(
join_column_index(left.headers, left_key),
join_column_index(right.headers, right_key),
) {
(Some(left_index), Some(right_index)) => {
let headers = join_headers(left.headers, right.headers, right_key)
let rows : Array[Array[String]] = Array::new()
for left_row in left.rows {
let key = join_cell_at(left_row, left_index)
let mut matched = false
for right_row in right.rows {
if join_cell_at(right_row, right_index) == key {
matched = true
rows.push(
join_merge_row(left_row, right_row, right.headers, right_key),
)
}
}
if keep_unmatched_left && !matched {
rows.push(join_merge_row(left_row, [], right.headers, right_key))
}
}
{ headers, rows }
}
_ => { headers: [], rows: [] }
}
}
///|
fn join_headers(
left_headers : Array[String],
right_headers : Array[String],
right_key : String,
) -> Array[String] {
let headers = left_headers.copy()
for header in right_headers {
if header != right_key {
if join_column_index(headers, header) is Some(_) {
headers.push("right_" + header)
} else {
headers.push(header)
}
}
}
headers
}
///|
fn join_merge_row(
left_row : Array[String],
right_row : Array[String],
right_headers : Array[String],
right_key : String,
) -> Array[String] {
let row = left_row.copy()
for i in 0.. Array[String] {
let result : Array[String] = Array::new()
for header in target_headers {
match join_column_index(source_headers, header) {
Some(index) => result.push(join_cell_at(row, index))
None => result.push("")
}
}
result
}
///|
fn join_column_index(headers : Array[String], column : String) -> Int? {
for i in 0.. String {
if index >= 0 && index < row.length() {
row[index]
} else {
""
}
}
///|
fn join_headers_equal(left : Array[String], right : Array[String]) -> Bool {
if left.length() != right.length() {
return false
}
for i in 0.. Bool {
for item in values {
if item == value {
return true
}
}
false
}