package com.github.martincooper.datatable; import com.github.martincooper.datatable.sorting.DataSort; import com.github.martincooper.datatable.sorting.SortItem; import com.github.martincooper.datatable.sorting.SortOrder; import io.vavr.collection.Map; import io.vavr.collection.Seq; import io.vavr.collection.Stream; import io.vavr.collection.Vector; import io.vavr.control.Try; import java.util.Iterator; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import static io.vavr.API.*; import static io.vavr.Patterns.*; /** * DataTable class. * Created by Martin Cooper on 08/07/2017. */ public class DataTable implements IBaseTable { private final String name; private final DataRowCollectionModifiable rows; private final DataColumnCollection columns; /** * Private DataTable constructor. Empty Table with no columns. * Use 'build' to create instance. * * @param tableName The name of the table. */ private DataTable(String tableName) { this.name = tableName; this.columns = new DataColumnCollection(this); this.rows = DataRowCollectionModifiable.build(this); } /** * Private DataTable Constructor. * Use 'build' to create instance. * * @param tableName The name of the table. * @param columns The collection of columns in the table. */ private DataTable(String tableName, Iterable<IDataColumn> columns) { this.name = tableName; this.columns = new DataColumnCollection(this, columns); this.rows = DataRowCollectionModifiable.build(this); } /** * Returns an iterator over elements of type DataRow. * * @return an Iterator. */ @Override public Iterator<DataRow> iterator() { return this.rows.iterator(); } /** * The name of the table. * * @return Returns the table name. */ @Override public String name() { return this.name; } /** * The column collection. * * @return Returns the columns collection. */ @Override public DataColumnCollection columns() { return this.columns; } /** * The row collection. * * @return Returns the row collection. */ @Override public DataRowCollectionModifiable rows() { return this.rows; } /** * Returns the rowCount / row count of the table. * * @return The row count of the table. */ @Override public Integer rowCount() { return this.columns.count() > 0 ? this.columns.get(0).data().length() : 0; } /** * Return a new DataTable based on this table (clone). * * @return Returns a clone of this DataTable. */ @Override public DataTable toDataTable() { return DataTable.build(this.name, this.columns).get(); } /** * Return a new Data View based on this table. * * @return A new Data View based on this table. */ @Override public DataView toDataView() { return DataView.build(this, this.rows).get(); } /** * Filters the row data using the specified predicate, * returning the results as a DataView over the original table. * * @param predicate The filter criteria. * @return Returns a DataView with the filter results. */ public DataView filter(Predicate<DataRow> predicate) { return this.rows.filter(predicate); } /** * Map operation across the Data Rows in the table. * * @param mapper The mapper function. * @param <U> The return type. * @return Returns the mapped results. */ public <U> Seq<U> map(Function<? super DataRow, ? extends U> mapper) { return this.rows.map(mapper); } /** * FlatMap implementation for the DataRowCollection class. * * @param <U> Mapped return type. * @param mapper The map function. * @return Returns a sequence of the applied flatMap. */ public <U> Seq<U> flatMap(Function<? super DataRow, ? extends Iterable <? extends U>> mapper) { return this.rows.flatMap(mapper); } /** * Reduce implementation for the DataRowCollection class. * * @param reducer The reduce function. * @return Returns a single, reduced DataRow. */ public DataRow reduce(BiFunction<? super DataRow, ? super DataRow, ? extends DataRow> reducer) { return this.rows.reduce(reducer); } /** * GroupBy implementation for the DataRowCollection class. * * @param grouper The group by function. * @return Returns a map containing the grouped data. */ public <C> Map<C, Vector<DataRow>> groupBy(Function<? super DataRow, ? extends C> grouper) { return this.rows.groupBy(grouper); } /** * Fold Left implementation for the DataRowCollection class. * * @param <U> Fold return type. * @param folder The fold function. * @return Returns a single value of U. */ public <U> U foldLeft(U zero, BiFunction<? super U, ? super DataRow, ? extends U> folder) { return this.rows.foldLeft(zero, folder); } /** * Fold Right implementation for the DataRowCollection class. * * @param <U> Fold return type. * @param folder The fold function. * @return Returns a single value of U. */ public <U> U foldRight(U zero, BiFunction<? super DataRow, ? super U, ? extends U> folder) { return this.rows.foldRight(zero, folder); } /** * Accessor to a specific row by index. * * @return Returns a single row. */ public DataRow row(Integer rowIdx) { return this.rows.get(rowIdx); } /** * Accessor to a specific column by index. * * @param colIdx The index of the column to return. * @return Returns a single column. */ public IDataColumn column(Integer colIdx) { return this.columns.get(colIdx); } /** * Accessor to a specific column by name. * * @param colName The name of the column to return. * @return Returns a single column. */ public IDataColumn column(String colName) { return this.columns.get(colName); } /** * Table QuickSort by single column name. * * @param columnName The column name to sort. * @return Returns the results as a sorted Data View. */ @Override public Try<DataView> quickSort(String columnName) { return this.quickSort(columnName, SortOrder.Ascending); } /** * Table QuickSort by single column name and a sort order. * * @param columnName The column name to sort. * @param sortOrder The sort order. * @return Returns the results as a sorted Data View. */ @Override public Try<DataView> quickSort(String columnName, SortOrder sortOrder) { SortItem sortItem = new SortItem(columnName, sortOrder); return DataSort.quickSort(this, this.rows.asSeq(), Stream.of(sortItem)); } /** * Table QuickSort by single column index. * * @param columnIndex The column index to sort. * @return Returns the results as a sorted Data View. */ @Override public Try<DataView> quickSort(Integer columnIndex) { return quickSort(columnIndex, SortOrder.Ascending); } /** * Table QuickSort by single column index and a sort order. * * @param columnIndex The column index to sort. * @param sortOrder The sort order. * @return Returns the results as a sorted Data View. */ @Override public Try<DataView> quickSort(Integer columnIndex, SortOrder sortOrder) { SortItem sortItem = new SortItem(columnIndex, sortOrder); return DataSort.quickSort(this, this.rows.asSeq(), Stream.of(sortItem)); } /** * Table QuickSort by single sort item. * * @param sortItem The sort item. * @return Returns the results as a sorted Data View. */ @Override public Try<DataView> quickSort(SortItem sortItem) { return this.quickSort(Stream.of(sortItem)); } /** * Table QuickSort by multiple sort items. * * @param sortItems The sort items. * @return Returns the results as a sorted Data View. */ @Override public Try<DataView> quickSort(Iterable<SortItem> sortItems) { return DataSort.quickSort(this, this.rows.asSeq(), Stream.ofAll(sortItems)); } /** * Builds an instance of a DataTable. * * @param tableName The name of the table. * @return Returns an instance of a DataTable. */ public static DataTable build(String tableName) { return new DataTable(tableName); } /** * Builds an instance of a DataTable. * Columns are validated before creation, returning a Failure on error. * * @param tableName The name of the table. * @param columns The column collection. * @return Returns a DataTable wrapped in a Try. */ public static Try<DataTable> build(String tableName, IDataColumn[] columns) { return build(tableName, Stream.of(columns)); } /** * Builds an instance of a DataTable. * Columns are validated before creation, returning a Failure on error. * * @param tableName The name of the table. * @param columns The column collection. * @return Returns a DataTable wrapped in a Try. */ public static Try<DataTable> build(String tableName, Iterable<IDataColumn> columns) { return build(tableName, Stream.ofAll(columns)); } /** * Builds an instance of a DataTable. * Columns are validated before creation, returning a Failure on error. * * @param tableName The name of the table. * @param columns The column collection. * @return Returns a DataTable wrapped in a Try. */ public static Try<DataTable> build(String tableName, Stream<IDataColumn> columns) { return Match(validateColumns(columns)).of( Case($Success($()), cols -> Try.success(new DataTable(tableName, cols))), Case($Failure($()), Try::failure) ); } /** * Validates the column data. * * @param columns The columns to validate. * @return Returns a Success or Failure. */ private static Try<Seq<IDataColumn>> validateColumns(Iterable<IDataColumn> columns) { return validateColumnNames(Stream.ofAll(columns)) .flatMap(DataTable::validateColumnDataLength); } /** * Validates there are no duplicate columns names. * * @param columns The columns to check. * @return Returns a Success or Failure. */ private static Try<Seq<IDataColumn>> validateColumnNames(Seq<IDataColumn> columns) { return columns.groupBy(IDataColumn::name).length() != columns.length() ? DataTableException.tryError("Columns contain duplicate names.") : Try.success(columns); } /** * Validates the number of items in each column is the same. * * @param columns The columns to check. * @return Returns a Success or Failure. */ private static Try<Seq<IDataColumn>> validateColumnDataLength(Seq<IDataColumn> columns) { return columns.groupBy(col -> col.data().length()).length() > 1 ? DataTableException.tryError("Columns have different lengths.") : Try.success(columns); } }