|
|
@@ -12,6 +12,7 @@ import net.ranides.assira.collection.query.CQuery;
|
|
|
import net.ranides.assira.collection.query.CQueryAbstract;
|
|
|
import net.ranides.assira.collection.sets.SetUtils;
|
|
|
import net.ranides.assira.generic.CompareUtils;
|
|
|
+import net.ranides.assira.math.MathUtils;
|
|
|
import net.ranides.assira.reflection.BeanModel;
|
|
|
|
|
|
import java.util.AbstractMap.SimpleImmutableEntry;
|
|
|
@@ -23,6 +24,17 @@ import java.util.stream.Stream;
|
|
|
|
|
|
import static java.util.stream.Collectors.toList;
|
|
|
|
|
|
+/**
|
|
|
+ * Grid represents two dimensional table of values: list of cells of type T.
|
|
|
+ *
|
|
|
+ * The whole idea is to allow processing of data in columns, rows or as a list of records
|
|
|
+ * Grid can contain header which maps column names into column indexes.
|
|
|
+ *
|
|
|
+ * Using header you can view every column as a field with specified name,
|
|
|
+ * especially you can view list of rows as a list of records represented by maps.
|
|
|
+ *
|
|
|
+ * @param <T> type of values
|
|
|
+ */
|
|
|
public class Grid<T> {
|
|
|
|
|
|
private final List<String> head;
|
|
|
@@ -58,6 +70,15 @@ public class Grid<T> {
|
|
|
return out;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Creates new grid from records. Every records should have the same structure.
|
|
|
+ * Header is created from list of keys inside records.
|
|
|
+ * List of cells is created from maps (using already generated header)
|
|
|
+ *
|
|
|
+ * @param records records
|
|
|
+ * @param <T> type of cell
|
|
|
+ * @return grid
|
|
|
+ */
|
|
|
public static <T> Grid<T> fromMap(List<? extends Map<String, T>> records) {
|
|
|
List<String> head = records.stream()
|
|
|
.flatMap(map -> map.keySet().stream())
|
|
|
@@ -65,16 +86,34 @@ public class Grid<T> {
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
List<List<T>> data = records.stream()
|
|
|
- .map((Map<String, T> r) -> head.stream().map(r::get).collect(toList()))
|
|
|
- .collect(toList());
|
|
|
+ .map((Map<String, T> r) -> head.stream().map(r::get).collect(Collectors.toList()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
|
return new Grid<>(head, data);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Creates new grid from records. Every records should have the same structure.
|
|
|
+ * Every object is converted into record using BeanModel.
|
|
|
+ * Header is created from list of keys inside records.
|
|
|
+ * List of cells is created from maps (using already generated header)
|
|
|
+ *
|
|
|
+ * @param rows rows
|
|
|
+ * @return grid
|
|
|
+ */
|
|
|
public static Grid<Object> fromObjects(List<?> rows) {
|
|
|
return fromMap(rows.stream().map(v -> BeanModel.typefor(v).fluent(v)).collect(toList()));
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Creates new grid from cells.
|
|
|
+ * It uses provided header to define mapping between column indexes and column names.
|
|
|
+ *
|
|
|
+ * @param header header
|
|
|
+ * @param rows rows
|
|
|
+ * @param <T> type of cell
|
|
|
+ * @return grid
|
|
|
+ */
|
|
|
public static <T> Grid<T> fromRows(List<String> header, List<List<T>> rows) {
|
|
|
return new Grid<>(header, rows);
|
|
|
}
|
|
|
@@ -101,58 +140,146 @@ public class Grid<T> {
|
|
|
return Objects.hash(head, data);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns header defined for this grid.
|
|
|
+ * Header can be used as a query with column names, to process them sequentialy.
|
|
|
+ * Header implements fast lookup for names: "indexOf" returns result in O(1) time.
|
|
|
+ *
|
|
|
+ * @return header
|
|
|
+ */
|
|
|
public GridHeader header() {
|
|
|
return header;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns cells organized in rows.
|
|
|
+ * Effectively, returned query is backed by list generated once.
|
|
|
+ *
|
|
|
+ * @return rows
|
|
|
+ */
|
|
|
public GridRows rows() {
|
|
|
return rows;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns cells organized in columns.
|
|
|
+ * Effectively, returned query is backed by list generated once.
|
|
|
+ *
|
|
|
+ * @return columns
|
|
|
+ */
|
|
|
public GridColumns columns() {
|
|
|
return columns;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns new grid with selected rows.
|
|
|
+ *
|
|
|
+ * @param selector list of row indexes to select
|
|
|
+ * @return grid
|
|
|
+ */
|
|
|
public Grid<T> rows(IntList selector) {
|
|
|
return new Grid<>(head, headmap, IntListUtils.map(selector, data::get));
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns new grid with selected columns
|
|
|
+ *
|
|
|
+ * @param selector list of column indexes to select
|
|
|
+ * @return grid
|
|
|
+ */
|
|
|
public Grid<T> columns(IntList selector) {
|
|
|
return new Grid<>(IntListUtils.map(selector, head::get), data.stream().map(row -> IntListUtils.map(selector, row::get)).collect(toList()));
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns cells as list of lists.
|
|
|
+ *
|
|
|
+ * @return cells
|
|
|
+ */
|
|
|
public List<List<T>> list() {
|
|
|
return data;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns all cells as stream. Every cell is returned as GridCursor:
|
|
|
+ * cursor gives access to cell value and its position (row and column index).
|
|
|
+ *
|
|
|
+ * @return stream with cells
|
|
|
+ */
|
|
|
public CQuery<GridCursor> stream() {
|
|
|
return rows().flat(r -> r);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns all rows as records (maps with keys defined by header).
|
|
|
+ *
|
|
|
+ * @return list of rows
|
|
|
+ */
|
|
|
public List<Map<String, T>> records() {
|
|
|
return ListUtils.map(data, GridRecord::new);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns cell at specified position.
|
|
|
+ *
|
|
|
+ * @param row row
|
|
|
+ * @param column column
|
|
|
+ * @return cell value
|
|
|
+ */
|
|
|
public T get(int row, int column) {
|
|
|
return data.get(row).get(column);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns cell at specified position.
|
|
|
+ *
|
|
|
+ * @param row row
|
|
|
+ * @param column column
|
|
|
+ * @return cell value
|
|
|
+ */
|
|
|
public T get(int row, String column) {
|
|
|
return data.get(row).get(headmap.getInt(column));
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Changes cell value at specified position
|
|
|
+ * @param row row
|
|
|
+ * @param column column
|
|
|
+ * @param value value
|
|
|
+ */
|
|
|
public void set(int row, int column, T value) {
|
|
|
data.get(row).set(column, value);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Changes cell value at specified position
|
|
|
+ * @param row row
|
|
|
+ * @param column column
|
|
|
+ * @param value value
|
|
|
+ */
|
|
|
public void set(int row, String column, T value) {
|
|
|
data.get(row).set(headmap.getInt(column), value);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns new grid with sorted rows by specified columns.
|
|
|
+ * Order of columns inside selector is important and defines comparision priority.
|
|
|
+ *
|
|
|
+ * @param selector list of columns to use in sort
|
|
|
+ * @return grid
|
|
|
+ */
|
|
|
public Grid<T> sort(IntList selector) {
|
|
|
return new Grid<>(head, headmap, data.stream().sorted((a, b) -> rowcmp(selector, a, b)).collect(toList()));
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Transform every cell into new value.
|
|
|
+ * Mapping function gets GridCursor, which means you can use not only cell value, but cell position too.
|
|
|
+ *
|
|
|
+ * @param mapper mapper
|
|
|
+ * @param <R> new cell type
|
|
|
+ * @return grid
|
|
|
+ */
|
|
|
public <R> Grid<R> map(Function<GridCursor, R> mapper) {
|
|
|
List<List<R>> out = new ArrayList<>(data.size());
|
|
|
for(int r=0, R=rows.size(); r<R; r++) {
|
|
|
@@ -176,6 +303,10 @@ public class Grid<T> {
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+ private boolean isValid(int row, int column) {
|
|
|
+ return row < data.size() && column < data.get(row).size();
|
|
|
+ }
|
|
|
+
|
|
|
@RequiredArgsConstructor
|
|
|
private class GridRecord extends AMap<String, T> {
|
|
|
|
|
|
@@ -207,6 +338,10 @@ public class Grid<T> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * GridHeader is essentially CQuery<String> backed by list, but with opitimized lookup by name.
|
|
|
+ * Method "indexOf(name)" will return in constant time.
|
|
|
+ */
|
|
|
public class GridHeader extends CQueryAbstract<String> {
|
|
|
|
|
|
private GridHeader() {
|
|
|
@@ -243,8 +378,20 @@ public class Grid<T> {
|
|
|
return head.iterator();
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public int indexOf(String value) {
|
|
|
+ return headmap.get(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int lastIndexOf(String value) {
|
|
|
+ return headmap.get(value);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * GridColumns is essentially CQuery backed by list of columns.
|
|
|
+ */
|
|
|
public class GridColumns extends CQueryAbstract<GridColumn> {
|
|
|
|
|
|
private final List<GridColumn> list;
|
|
|
@@ -273,6 +420,11 @@ public class Grid<T> {
|
|
|
return head.size();
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public boolean hasFastList() {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public Stream<GridColumn> stream() {
|
|
|
return list.stream();
|
|
|
@@ -283,12 +435,20 @@ public class Grid<T> {
|
|
|
return list.iterator();
|
|
|
}
|
|
|
|
|
|
- public GridColumn get(int index) {
|
|
|
- return list.get(index);
|
|
|
+ @Override
|
|
|
+ public List<GridColumn> list() {
|
|
|
+ return list;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * GridColumn is essentially CQuery not backed by any in-memory structure.
|
|
|
+ * GridColumn is a view and reads cells in realtime, by just using column index as bound argument.
|
|
|
+ *
|
|
|
+ * It allows iteration through all rows and extract cell in specified column.
|
|
|
+ * It allows fast read/write to cell using row index.
|
|
|
+ */
|
|
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
|
|
public class GridColumn extends CQueryAbstract<GridCursor> {
|
|
|
|
|
|
@@ -324,15 +484,38 @@ public class Grid<T> {
|
|
|
return stream().iterator();
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public Optional<GridCursor> at(int row) {
|
|
|
+ return isValid(row, index) ? Optional.of(new GridCursor(row, index)) : Optional.empty();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Fast access to cell data inside column.
|
|
|
+ * @param row row
|
|
|
+ * @return cell value
|
|
|
+ */
|
|
|
public T get(int row) {
|
|
|
return data.get(row).get(index);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Fast access to cell data inside column.
|
|
|
+ * @param row row
|
|
|
+ * @param value new value
|
|
|
+ * @return old value
|
|
|
+ */
|
|
|
+ public T set(int row, T value) {
|
|
|
+ return data.get(row).set(index, value);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * GridRows is essentially CQuery backed by list of rows.
|
|
|
+ */
|
|
|
public class GridRows extends CQueryAbstract<GridRow> {
|
|
|
|
|
|
- private final List<GridRow> list = IntStream.range(0, size()).mapToObj(i -> new GridRow(i)).collect(toList());
|
|
|
+ private final List<GridRow> list = IntStream.range(0, size())
|
|
|
+ .mapToObj(i -> new GridRow(i)).collect(Collectors.toList());
|
|
|
|
|
|
private GridRows() {
|
|
|
// do nothing
|
|
|
@@ -353,6 +536,11 @@ public class Grid<T> {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public boolean hasFastList() {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public int size() {
|
|
|
return data.size();
|
|
|
@@ -368,12 +556,20 @@ public class Grid<T> {
|
|
|
return list.iterator();
|
|
|
}
|
|
|
|
|
|
- public GridRow get(int index) {
|
|
|
- return list.get(index);
|
|
|
+ @Override
|
|
|
+ public List<GridRow> list() {
|
|
|
+ return list;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * GridRow is essentially CQuery not backed by any in-memory structure.
|
|
|
+ * GridRow is a view and reads cells in realtime, by just using rows index as bound argument.
|
|
|
+ *
|
|
|
+ * It allows iteration through all columns and extract cells in specified row.
|
|
|
+ * It allows fast read/write to cell using column index.
|
|
|
+ */
|
|
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
|
|
public class GridRow extends CQueryAbstract<GridCursor> {
|
|
|
|
|
|
@@ -409,16 +605,58 @@ public class Grid<T> {
|
|
|
return stream().iterator();
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public Optional<GridCursor> at(int column) {
|
|
|
+ return isValid(index, column) ? Optional.of(new GridCursor(index, column)) : Optional.empty();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Fast access to cell data inside row.
|
|
|
+ *
|
|
|
+ * @param column column
|
|
|
+ * @return value
|
|
|
+ */
|
|
|
public T get(int column) {
|
|
|
return data.get(index).get(column);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Fast access to cell data inside row.
|
|
|
+ *
|
|
|
+ * @param column column
|
|
|
+ * @return value
|
|
|
+ */
|
|
|
public T get(String column) {
|
|
|
return data.get(index).get(headmap.getInt(column));
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Fast access to cell data inside row.
|
|
|
+ *
|
|
|
+ * @param column column
|
|
|
+ * @param value new value
|
|
|
+ * @return old value
|
|
|
+ */
|
|
|
+ public T set(int column, T value) {
|
|
|
+ return data.get(index).set(column, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Fast access to cell data inside row.
|
|
|
+ *
|
|
|
+ * @param column column
|
|
|
+ * @param value new value
|
|
|
+ * @return old value
|
|
|
+ */
|
|
|
+ public T set(String column, T value) {
|
|
|
+ return data.get(index).set(headmap.getInt(column), value);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
+ * It is simple view exposing cell's value and its position
|
|
|
+ */
|
|
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
|
|
public class GridCursor {
|
|
|
|
|
|
@@ -426,20 +664,41 @@ public class Grid<T> {
|
|
|
|
|
|
private final int column;
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns cell's position
|
|
|
+ *
|
|
|
+ * @return row index
|
|
|
+ */
|
|
|
public int row() {
|
|
|
return row;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns cell's position
|
|
|
+ *
|
|
|
+ * @return column index
|
|
|
+ */
|
|
|
public int column() {
|
|
|
return column;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Fast access to cell data at cursor position.
|
|
|
+ *
|
|
|
+ * @return value
|
|
|
+ */
|
|
|
public T get() {
|
|
|
return data.get(row).get(column);
|
|
|
}
|
|
|
|
|
|
- public void set(T value) {
|
|
|
- data.get(row).set(column, value);
|
|
|
+ /**
|
|
|
+ * Fast access to cell data at cursor position.
|
|
|
+ *
|
|
|
+ * @param value new value
|
|
|
+ * @return old value
|
|
|
+ */
|
|
|
+ public T set(T value) {
|
|
|
+ return data.get(row).set(column, value);
|
|
|
}
|
|
|
|
|
|
}
|