///|
pub(all) struct TableTransformError {
code : String
message : String
} derive(Eq)
///|
fn table_transform_error(
code : String,
message : String,
) -> TableTransformError {
{ code, message }
}
///|
fn clone_header(header : Array[String]) -> Array[String] {
let copy : Array[String] = []
let mut i = 0
while i < header.length() {
copy.push(header[i])
i = i + 1
}
copy
}
///|
fn clone_row(row : Array[String]) -> Array[String] {
let copy : Array[String] = []
let mut i = 0
while i < row.length() {
copy.push(row[i])
i = i + 1
}
copy
}
///|
fn clone_rows(rows : Array[Array[String]]) -> Array[Array[String]] {
let copy : Array[Array[String]] = []
let mut i = 0
while i < rows.length() {
copy.push(clone_row(rows[i]))
i = i + 1
}
copy
}
///|
fn header_index(header : Array[String], name : String) -> Int? {
let mut i = 0
while i < header.length() {
if header[i] == name {
return Some(i)
}
i = i + 1
}
None
}
///|
fn header_has_name(header : Array[String], name : String) -> Bool {
match header_index(header, name) {
Some(_) => true
None => false
}
}
///|
fn header_equal(left : Array[String], right : Array[String]) -> Bool {
if left.length() != right.length() {
return false
}
let mut i = 0
while i < left.length() {
if left[i] != right[i] {
return false
}
i = i + 1
}
true
}
///|
fn row_equal(left : Array[String], right : Array[String]) -> Bool {
if left.length() != right.length() {
return false
}
let mut i = 0
while i < left.length() {
if left[i] != right[i] {
return false
}
i = i + 1
}
true
}
///|
fn names_to_indices(
header : Array[String],
names : Array[String],
) -> Result[Array[Int], TableTransformError] {
let indices : Array[Int] = []
let mut i = 0
while i < names.length() {
match header_index(header, names[i]) {
Some(index) => indices.push(index)
None => {
let msg =
$|column '\{names[i]}' does not exist in table
return Err(table_transform_error("missing_column", msg))
}
}
i = i + 1
}
Ok(indices)
}
///|
fn build_table_from_indices(table : Table, indices : Array[Int]) -> Table {
let header : Array[String] = []
let mut i = 0
while i < indices.length() {
header.push(table.header[indices[i]])
i = i + 1
}
let rows : Array[Array[String]] = []
let mut r = 0
while r < table.rows.length() {
let row = table.rows[r]
let selected : Array[String] = []
let mut c = 0
while c < indices.length() {
let index = indices[c]
let value = match row.get(index) {
Some(v) => v
None => ""
}
selected.push(value)
c = c + 1
}
rows.push(selected)
r = r + 1
}
{ header, rows }
}
///|
fn row_has_blank(row : Array[String]) -> Bool {
let mut i = 0
while i < row.length() {
if !row[i].trim().is_empty() {
return false
}
i = i + 1
}
true
}
///|
fn find_duplicate_row(
rows : Array[Array[String]],
candidate : Array[String],
) -> Bool {
let mut i = 0
while i < rows.length() {
if row_equal(rows[i], candidate) {
return true
}
i = i + 1
}
false
}
///|
fn join_right_header(
left_header : Array[String],
right_header : Array[String],
right_key : String,
) -> Array[String] {
let header : Array[String] = clone_header(left_header)
let mut i = 0
while i < right_header.length() {
let name = right_header[i]
if name != right_key {
if header_has_name(header, name) {
let right_name =
$|right.\{name}
header.push(right_name)
} else {
header.push(name)
}
}
i = i + 1
}
header
}
///|
fn join_row(
left_row : Array[String],
right_row : Array[String],
right_header : Array[String],
right_key : String,
) -> Array[String] {
let row : Array[String] = clone_row(left_row)
let mut i = 0
while i < right_header.length() {
let name = right_header[i]
if name != right_key {
let value = match right_row.get(i) {
Some(v) => v
None => ""
}
row.push(value)
}
i = i + 1
}
row
}
///|
pub fn Table::head(self : Table, n : Int) -> Table {
let limit = if n < 0 {
0
} else if n > self.rows.length() {
self.rows.length()
} else {
n
}
let rows : Array[Array[String]] = []
let mut i = 0
while i < limit {
rows.push(clone_row(self.rows[i]))
i = i + 1
}
{ header: clone_header(self.header), rows }
}
///|
pub fn Table::tail(self : Table, n : Int) -> Table {
let size = self.rows.length()
let start = if n <= 0 { size } else if n >= size { 0 } else { size - n }
let rows : Array[Array[String]] = []
let mut i = start
while i < size {
rows.push(clone_row(self.rows[i]))
i = i + 1
}
{ header: clone_header(self.header), rows }
}
///|
pub fn Table::slice_rows(self : Table, start : Int, end : Int) -> Table {
let size = self.rows.length()
let from = if start < 0 { 0 } else if start > size { size } else { start }
let to = if end < from { from } else if end > size { size } else { end }
let rows : Array[Array[String]] = []
let mut i = from
while i < to {
rows.push(clone_row(self.rows[i]))
i = i + 1
}
{ header: clone_header(self.header), rows }
}
///|
pub fn Table::select_columns(
self : Table,
names : Array[String],
) -> Result[Table, TableTransformError] {
match names_to_indices(self.header, names) {
Ok(indices) => Ok(build_table_from_indices(self, indices))
Err(err) => Err(err)
}
}
///|
pub fn Table::drop_columns(self : Table, names : Array[String]) -> Table {
let indices : Array[Int] = []
let mut i = 0
while i < self.header.length() {
if !header_has_name(names, self.header[i]) {
indices.push(i)
}
i = i + 1
}
build_table_from_indices(self, indices)
}
///|
pub fn Table::rename_column(
self : Table,
from : String,
to : String,
) -> Result[Table, TableTransformError] {
match header_index(self.header, from) {
Some(index) => {
if from != to && header_has_name(self.header, to) {
let msg =
$|target column '\{to}' already exists
return Err(table_transform_error("duplicate_header", msg))
}
let header = clone_header(self.header)
header[index] = to
Ok({ header, rows: clone_rows(self.rows) })
}
None => {
let msg =
$|column '\{from}' not found
Err(table_transform_error("missing_column", msg))
}
}
}
///|
pub fn Table::rename_columns(
self : Table,
renames : Array[(String, String)],
) -> Result[Table, TableTransformError] {
let header = clone_header(self.header)
let mut i = 0
while i < renames.length() {
let (from, to) = renames[i]
match header_index(header, from) {
Some(index) => {
if from != to && header_has_name(header, to) {
let msg =
$|target column '\{to}' already exists
return Err(table_transform_error("duplicate_header", msg))
}
header[index] = to
}
None => {
let msg =
$|column '\{from}' not found
return Err(table_transform_error("missing_column", msg))
}
}
i = i + 1
}
Ok({ header, rows: clone_rows(self.rows) })
}
///|
pub fn Table::append_column(
self : Table,
name : String,
default_value : String,
) -> Result[Table, TableTransformError] {
if header_has_name(self.header, name) {
let msg =
$|column '\{name}' already exists
return Err(table_transform_error("duplicate_header", msg))
}
let header = clone_header(self.header)
header.push(name)
let rows : Array[Array[String]] = []
let mut i = 0
while i < self.rows.length() {
let row = clone_row(self.rows[i])
row.push(default_value)
rows.push(row)
i = i + 1
}
Ok({ header, rows })
}
///|
pub fn Table::replace_column_values(
self : Table,
name : String,
values : Array[String],
) -> Result[Table, TableTransformError] {
match header_index(self.header, name) {
Some(index) => {
if values.length() != self.rows.length() {
let msg =
$|column '\{name}' expects \{self.rows.length()} values, got \{values.length()}
return Err(table_transform_error("length_mismatch", msg))
}
let rows : Array[Array[String]] = []
let mut i = 0
while i < self.rows.length() {
let row = clone_row(self.rows[i])
row[index] = values[i]
rows.push(row)
i = i + 1
}
Ok({ header: clone_header(self.header), rows })
}
None => {
let msg =
$|column '\{name}' not found
Err(table_transform_error("missing_column", msg))
}
}
}
///|
pub fn Table::fill_empty_cells(self : Table, replacement : String) -> Table {
let rows : Array[Array[String]] = []
let mut r = 0
while r < self.rows.length() {
let row = clone_row(self.rows[r])
let mut c = 0
while c < row.length() {
if row[c].trim().is_empty() {
row[c] = replacement
}
c = c + 1
}
rows.push(row)
r = r + 1
}
{ header: clone_header(self.header), rows }
}
///|
pub fn Table::fill_missing_in_column(
self : Table,
name : String,
replacement : String,
) -> Result[Table, TableTransformError] {
match header_index(self.header, name) {
Some(index) => {
let rows : Array[Array[String]] = []
let mut r = 0
while r < self.rows.length() {
let row = clone_row(self.rows[r])
if row.get(index) is None {
while row.length() <= index {
row.push("")
}
row[index] = replacement
} else if row[index].trim().is_empty() {
row[index] = replacement
}
rows.push(row)
r = r + 1
}
Ok({ header: clone_header(self.header), rows })
}
None => {
let msg =
$|column '\{name}' not found
Err(table_transform_error("missing_column", msg))
}
}
}
///|
pub fn Table::trim_cells(self : Table) -> Table {
let rows : Array[Array[String]] = []
let mut r = 0
while r < self.rows.length() {
let row = clone_row(self.rows[r])
let mut c = 0
while c < row.length() {
row[c] = row[c].trim().to_owned()
c = c + 1
}
rows.push(row)
r = r + 1
}
{ header: clone_header(self.header), rows }
}
///|
pub fn Table::remove_blank_rows(self : Table) -> Table {
let rows : Array[Array[String]] = []
let mut r = 0
while r < self.rows.length() {
let row = self.rows[r]
if !row_has_blank(row) {
rows.push(clone_row(row))
}
r = r + 1
}
{ header: clone_header(self.header), rows }
}
///|
pub fn Table::dedupe_rows(self : Table) -> Table {
let rows : Array[Array[String]] = []
let mut r = 0
while r < self.rows.length() {
let row = self.rows[r]
if !find_duplicate_row(rows, row) {
rows.push(clone_row(row))
}
r = r + 1
}
{ header: clone_header(self.header), rows }
}
///|
pub fn Table::concat(
self : Table,
other : Table,
) -> Result[Table, TableTransformError] {
if !header_equal(self.header, other.header) {
return Err(
table_transform_error(
"header_mismatch", "tables can only be concatenated when their headers match exactly",
),
)
}
let rows = clone_rows(self.rows)
let mut i = 0
while i < other.rows.length() {
rows.push(clone_row(other.rows[i]))
i = i + 1
}
Ok({ header: clone_header(self.header), rows })
}
///|
pub fn Table::column_values(
self : Table,
name : String,
) -> Result[Array[String], TableTransformError] {
match header_index(self.header, name) {
Some(index) => {
let values : Array[String] = []
let mut i = 0
while i < self.rows.length() {
let value = match self.rows[i].get(index) {
Some(v) => v
None => ""
}
values.push(value)
i = i + 1
}
Ok(values)
}
None => {
let msg =
$|column '\{name}' not found
Err(table_transform_error("missing_column", msg))
}
}
}
///|
pub fn Table::value_counts(
self : Table,
name : String,
) -> Result[Array[(String, Int)], TableTransformError] {
match self.column_values(name) {
Ok(values) => {
let counts : Array[(String, Int)] = []
let mut i = 0
while i < values.length() {
let value = values[i]
let mut found = false
let mut j = 0
while j < counts.length() {
let (candidate, count) = counts[j]
if candidate == value {
counts[j] = (candidate, count + 1)
found = true
break
}
j = j + 1
}
if !found {
counts.push((value, 1))
}
i = i + 1
}
Ok(counts)
}
Err(err) => Err(err)
}
}
///|
pub fn Table::inner_join(
self : Table,
other : Table,
left_key : String,
right_key : String,
) -> Result[Table, TableTransformError] {
match header_index(self.header, left_key) {
Some(left_index) =>
match header_index(other.header, right_key) {
Some(right_index) => {
let header = join_right_header(self.header, other.header, right_key)
let rows : Array[Array[String]] = []
let mut left_row_index = 0
while left_row_index < self.rows.length() {
let left_row = self.rows[left_row_index]
let left_value = match left_row.get(left_index) {
Some(v) => v
None => ""
}
let mut right_row_index = 0
while right_row_index < other.rows.length() {
let right_row = other.rows[right_row_index]
let right_value = match right_row.get(right_index) {
Some(v) => v
None => ""
}
if left_value == right_value {
rows.push(
join_row(left_row, right_row, other.header, right_key),
)
}
right_row_index = right_row_index + 1
}
left_row_index = left_row_index + 1
}
Ok({ header, rows })
}
None => {
let msg =
$|column '\{right_key}' not found in right table
Err(table_transform_error("missing_column", msg))
}
}
None => {
let msg =
$|column '\{left_key}' not found in left table
Err(table_transform_error("missing_column", msg))
}
}
}