Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
780 lines (673 sloc) 15.8 KB
#~~
# CSV library
# Copyright (c) 2016-2019 Randy Hollines
~~#
use Collection.Generic;
#~
Process and perform calculations on CSV files (csv.obl)
~#
bundle Data.CSV {
#~
CSV row
~#
class CsvRow {
@table : CsvTable;
@id : Int;
@columns : CompareVector<String>;
@read_only : Bool;
#~
Constructor
@param table table that has row
@param id row id
@param read_only true if readonly, false otherwise
@param columns column values
~#
New(table : CsvTable, id : Int, read_only : Bool, columns : CompareVector<String>) {
@table := table;
@id := id;
@read_only := read_only;
@columns := columns;
}
#~
Gets the row ID
@return id
~#
method : public : Id() ~ Int {
return @id;
}
#~
Gets the indexed row value
@param index index
@return row value
~#
method : public : Get(index : Int) ~ String {
if(index < @columns->Size()) {
return @columns->Get(index);
};
return Nil;
}
#~
Gets the indexed row value
@param name name
@return row value
~#
method : public : Get(name : String) ~ String {
index := @table->GetRowId(name);
if(index < @columns->Size()) {
return @columns->Get(index);
};
return Nil;
}
#~
Sets the indexed row value
@param value value to set
@param index index
@return true of value was set, false otherwise
~#
method : public : Set(index : Int, value : String) ~ Bool {
if(@read_only = false & index < @columns->Size()) {
return @columns->Set(value->ReplaceAll("\"\"", "ˑ"), index);
};
return false;
}
#~
Sets the named row value
@param name name
@param value value
@return true of value was set, false otherwise
~#
method : public : Set(name : String, value : String) ~ Bool {
return Set(@table->GetRowId(name), value);
}
#~
Appends value to the end of the row
@value value to append
~#
method : public : Append(value : String) ~ Nil {
@columns->AddBack(value);
}
#~
Gets the row size
@return row size
~#
method : public : Size() ~ Int {
return @columns->Size();
}
#~
Calculates the row sum
@return row sum
~#
method : public : Sum() ~ Float {
return Sum(0, @columns->Size());
}
#~
Calculates the row sum
@param end ending column
@return row sum
~#
method : public : Sum(end : Int) ~ Float {
return Sum(0, end);
}
#~
Calculates the row sum
@param start starting column
@param end ending column
@return row sum
~#
method : public : Sum(start : Int, end : Int) ~ Float {
sum := 0.0;
length := end - start;
offset := start + length;
# "length={$length}, offset={$offset}"->PrintLine();
if(length > 0 & offset <= @columns->Size()) {
for(i := start; i < offset; i += 1;) {
sum += @columns->Get(i)->ToFloat();
};
};
return sum;
}
#~
Calculates the row average
@return row average
~#
method : public : Average() ~ Float {
return Average(0, @columns->Size());
}
#~
Calculates the row average
@param end ending column
@return row average
~#
method : public : Average(end : Int) ~ Float {
return Average(0, end);
}
#~
Calculates the row average
@param start starting column
@param end ending column
@return row average
~#
method : public : Average(start : Int, end : Int) ~ Float {
if(@columns->Size() = 0) {
return 0.0;
};
return Sum(start, end) / (end - start + 1);
}
#~
Calculates the row median
@return row median
~#
method : public : Median() ~ Float {
if(@columns->Size() = 1) {
@columns->Get(0)->ToFloat();
};
# store values and sort
sorted := CompareVector->New()<FloatHolder>;
each(i : @columns) {
sorted->AddBack(@columns->Get(i)->ToFloat());
};
sorted->Sort();
# find median
size := sorted->Size();
if(size % 2 = 0) {
right := sorted->Get(size / 2);
left := sorted->Get(right - 1);
return (left + right) / 2;
}
else {
return sorted->Get(size / 2);
};
}
#~
Formats the row into a string
@return string representation of the row
~#
method : public : ToString() ~ String {
buffer := "";
each(i : @columns) {
buffer->Append(@columns->Get(i)->ReplaceAll("ˑ", "\"\""));
if(i + 1 < @columns->Size()) {
buffer->Append(",");
};
};
return buffer;
}
}
#~
CSV column
~#
class CsvColumn {
@rows : Vector<String>;
#~
Constructor
@param rows row values
~#
New(rows : Vector<String>) {
@rows := rows;
}
#~
Gets the row size
@return row size
~#
method : public : Size() ~ Int {
return @rows->Size();
}
#~
Gets the indexed row value
@param index index
@return row value
~#
method : public : Get(index : Int) ~ String {
return @rows->Get(index);
}
#~
Calculates the column sum
@return column sum
~#
method : public : Sum() ~ Float {
return Sum(0, @rows->Size());
}
#~
Calculates the row sum
@param end ending column
@return row sum
~#
method : public : Sum(end : Int) ~ Float {
return Sum(0, end);
}
#~
Calculates the row sum
@param start starting column
@param end ending column
@return row sum
~#
method : public : Sum(start : Int, end : Int) ~ Float {
sum := 0.0;
length := end - start;
offset := start + length;
# "length={$length}, offset={$offset}"->PrintLine();
if(length > 0 & offset <= @rows->Size()) {
for(i := start; i < offset; i += 1;) {
sum += @rows->Get(i)->ToFloat();
};
};
return sum;
}
#~
Calculates the column average
@return column average
~#
method : public : Average() ~ Float {
return Average(0, @rows->Size());
}
#~
Calculates the column average
@param end ending column
@return column average
~#
method : public : Average(end : Int) ~ Float {
return Average(0, end);
}
#~
Calculates the column average
@param start starting column
@param end ending column
@return column average
~#
method : public : Average(start : Int, end : Int) ~ Float {
if(@rows->Size() = 0) {
return 0.0;
};
return Sum(start, end) / (end - start + 1);
}
#~
Calculates the column median
@return column median
~#
method : public : Median() ~ Float {
if(@rows->Size() = 1) {
@rows->Get(0)->ToFloat();
};
# store values and sort
sorted := CompareVector->New()<FloatHolder>;
each(i : @rows) {
sorted->AddBack(@rows->Get(i)->ToFloat());
};
sorted->Sort();
# find median
size := sorted->Size();
if(size % 2 = 0) {
right := sorted->Get(size / 2);
left := sorted->Get(right - 1);
return (left + right) / 2;
}
else {
return sorted->Get(size / 2);
};
}
}
#~
CSV table
~#
class CsvTable {
@data : Vector<CsvRow>;
@is_parsed : Bool;
@header_names : Hash<String, IntHolder>;
@ending : String;
#~
Constructor
@param data CSV data with CRNL line endings
~#
New(data : String) {
Init(data, "\r\n");
}
#~
Constructor
@param data CSV data
@param ending line ending
~#
New(data : String, ending : String) {
Init(data, ending);
}
method : Init(data : String, ending : String) ~ Nil {
@ending := ending;
@is_parsed := true;
rows := data->Split(@ending);
if(rows->Size() > 1) {
@data := ParseColumns(rows);
if(@data = Nil) {
@is_parsed := false;
};
}
else {
@is_parsed := false;
};
if(@is_parsed) {
@header_names := Hash->New()<String, IntHolder>;
headers := @data->Get(0)->As(CsvRow);
each(i : headers) {
@header_names->Insert(headers->Get(i), i);
};
};
}
New(data : Vector<CsvRow>, header_names : Hash<String, IntHolder>) {
@is_parsed := true;
@data := data;
@header_names := header_names;
}
#~
Returns rather the file has been successfully parsed
@return true if successfully parsed, false otherwise
~#
method : public : IsParsed() ~ Bool {
return @is_parsed;
}
#~
Gets row name
@param name name
@return row index
~#
method : public : GetRowId(name : String) ~ Int {
index := @header_names->Find(name);
if(index <> Nil) {
return index->Get();
};
return -1;
}
#~
Gets header names
@return header names
~#
method : public : GetHeaders() ~ CsvRow {
if(@is_parsed) {
return @data->Get(0)->As(CsvRow);
};
return Nil;
}
#~
Appends a column to the end of the table
@param name name of column to add
@return ture if column was added, false otherwise
~#
method : public : AppendColumn(name : String) ~ Bool {
if(@header_names->Has(name) = false & @is_parsed) {
@data->Get(0)->As(CsvRow)->Append(name);
for(i := 1; i < @data->Size(); i+=1;) {
@data->Get(i)->As(CsvRow)->Append("");
};
@header_names->Insert(name, RowSize() - 1);
return true;
};
return false;
}
#~
Removes a row from the table
@param id row to delete
@return true if deleted false otherwise
~#
method : public : Delete(id : Int) ~ Bool {
if(id = 0) {
return false;
};
return @data->Remove(id) <> Nil;
}
#~
Gets the size of rows
@return row size
~#
method : public : RowSize() ~ Int {
if(<>@is_parsed) {
return 0;
};
return @data->Get(0)->As(CsvRow)->Size();
}
method : native : ParseColumns(rows : String[]) ~ Vector<CsvRow> {
parsed_rows := Vector->New()<CsvRow>;
each(i : rows) {
parsed_rows->AddBack(CsvRow->New(@self, i, i = 0, ParseRow(rows[i]->ReplaceAll("\"\"", "ˑ"))));
};
if(rows->Size() <> parsed_rows->Size()) {
# IO.Console->Print("rows: parsed = ")->Print(rows->Size())->Print(", size = ")->PrintLine(parsed_rows->Size());
return Nil;
};
column_size := parsed_rows->Get(0)->Size();
each(i : parsed_rows) {
parsed_row := parsed_rows->Get(i)->As(CsvRow);
if(parsed_row->Size() < column_size) {
# IO.Console->Print("columns: parsed = ")->Print(parsed_row->Size())->Print(", size = ")->PrintLine(column_size);
return Nil;
};
};
return parsed_rows;
}
method : native : ParseRow(row : String) ~ CompareVector<String> {
columns := CompareVector->New()<String>;
index := 0;
while(index < row->Size()) {
if(row->Get(index) = '"') {
index += 1;
start := index;
while(index < row->Size() & row->Get(index) <> '"') {
index += 1;
};
token := row->SubString(start, index - start);
index += 1;
if(token <> Nil) {
columns->AddBack(token->ReplaceAll("ˑ", "\"\""));
};
# IO.Console->Print("quoted = |")->Print(token->ReplaceAll("ˑ", "\"\""))->PrintLine("|");
}
else {
start := index;
while(index < row->Size() & row->Get(index) <> ',') {
index += 1;
};
token := row->SubString(start, index - start);
if(token = Nil) {
token := "";
};
columns->AddBack(token->ReplaceAll("ˑ", "\"\""));
# IO.Console->Print("normal = |")->Print(token->ReplaceAll("ˑ", "\"\""))->PrintLine("|");
};
if(row->Get(index) = ',') {
index += 1;
if(index = row->Size()) {
columns->AddBack("");
# "normal = ||"->PrintLine();
};
};
};
return columns;
}
#~
Gets an indexed row
@param index index
@return row
~#
method : public : Get(index : Int) ~ CsvRow {
if(<>@is_parsed) {
return Nil;
};
return @data->Get(index)->As(CsvRow);
}
#~
Gets the number of rows
@return number of rows
~#
method : public : Size() ~ Int {
if(<>@is_parsed) {
return 0;
};
return @data->Size();
}
#~
Searches a given column for matching values
@param name column name
@param value value to search for
@return table of matching results
~#
method : public : Search(name : String, value : String) ~ CsvTable {
index := @header_names->Find(name);
if(index <> Nil) {
return Search(index->Get(), value);
};
return Nil;
}
#~
Searches a given column for matching values
@param index column index
@param value value to search for
@return table of matching results
~#
method : public : Search(index : Int, value : String) ~ CsvTable {
if(index >= RowSize()) {
return Nil;
};
found := Vector->New()<CsvRow>;
found->AddBack(@data->Get(0));
for(i := 1; i < @data->Size(); i +=1;) {
row := @data->Get(i);
if(row->Get(index)->Equals(value)) {
found->AddBack(row);
};
};
return CsvTable->New(found, @header_names);
}
#~
Searches a given column for a string that contains a given
@param name column name
@param value value to search for
@return true if value is found, false otherwise
~#
method : public : Contains(name : String, value : String) ~ Bool {
index := @header_names->Find(name);
if(index <> Nil) {
return Contains(index->Get(), value);
};
return false;
}
#~
Counts a column for matching values
@param name column name
@param value value to search for
@return number of occurrences
~#
method : public : Count(name : String, value : String) ~ Int {
index := @header_names->Find(name);
if(index <> Nil) {
return Count(index->Get(), value);
};
return 0;
}
#~
Counts a column for matching values
@param index column index
@param value value to search for
@return number of occurrences
~#
method : public : Count(index : Int, value : String) ~ Int {
if(index >= RowSize()) {
return 0;
};
count := 0;
for(i := 1; i < @data->Size(); i +=1;) {
row := @data->Get(i)->As(CsvRow);
if(row->Get(index)->Equals(value)) {
count += 1;
};
};
return count;
}
#~
Searches a given column for a string that contains a given
@param index column index
@param value value to search for
@return true if value is found, false otherwise
~#
method : public : Contains(index : Int, value : String) ~ Bool {
if(index >= RowSize()) {
return false;
};
for(i := 1; i < @data->Size(); i +=1;) {
row := @data->Get(i)->As(CsvRow);
if(row->Get(index)->Find(value) > -1) {
return true;
};
};
return false;
}
#~
Formats the table into a string
@return string representation of the table
~#
method : public : ToString() ~ String {
buffer := "";
each(i : @data) {
row := @data->Get(i)->As(CsvRow);
buffer->Append(row->ToString());
buffer->Append(@ending);
};
return buffer;
}
#~
Get unique values for a given row
@param name column name
@return unique values for the given row
~#
method : public : UniqueColumnValues(name : String) ~ CsvColumn {
index := @header_names->Find(name);
if(index <> Nil) {
return UniqueColumnValues(index->Get());
};
return Nil;
}
#~
Get values for a given row
@param index column index
@return values for the given row
~#
method : public : ColumnValues(index : Int) ~ CsvColumn {
if(index >= RowSize()) {
return Nil;
};
values := Vector->New()<String>;
for(i := 1; i < @data->Size(); i +=1;) {
value := @data->Get(i)->As(CsvRow)->Get(index);
values->AddBack(value);
};
return CsvColumn->New(values);
}
#~
Get values for a given row
@param name column name
@return values for the given row
~#
method : public : ColumnValues(name : String) ~ CsvColumn {
index := @header_names->Find(name);
if(index <> Nil) {
return ColumnValues(index->Get());
};
return Nil;
}
#~
Get unique values for a given row
@param index column index
@return unique values for the given row
~#
method : public : UniqueColumnValues(index : Int) ~ CsvColumn {
if(index >= RowSize()) {
return Nil;
};
values := Vector->New()<String>;
uniques := Set->New()<String>;
for(i := 1; i < @data->Size(); i +=1;) {
value := @data->Get(i)->As(CsvRow)->Get(index);
if(<>uniques->Has(value)) {
values->AddBack(value);
uniques->Insert(value);
};
};
return CsvColumn->New(values);
}
}
}
You can’t perform that action at this time.