From 059ade7973b07619d06f4d08f75c4c6706bfae49 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 24 Jun 2020 14:40:55 +0300 Subject: [PATCH] Table refactoring --- build.gradle.kts | 2 +- .../dataforge/meta/ConfigurableDelegate.kt | 5 + .../hep/dataforge/tables/ColumnTable.kt | 17 +-- .../kotlin/hep/dataforge/tables/MapRow.kt | 10 -- .../dataforge/tables/MutableColumnTable.kt | 32 +---- .../hep/dataforge/tables/MutableTable.kt | 2 +- .../kotlin/hep/dataforge/tables/RowTable.kt | 7 +- .../kotlin/hep/dataforge/tables/Table.kt | 47 +++---- .../dataforge/tables/TransformationColumn.kt | 60 ++++++++ .../hep/dataforge/tables/io/TextRows.kt | 5 +- .../{NumberColumn.kt => numericColumns.kt} | 15 +- docs/images/df.svg | 131 ++++++++++++++++++ 12 files changed, 236 insertions(+), 97 deletions(-) delete mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/TransformationColumn.kt rename dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/{NumberColumn.kt => numericColumns.kt} (83%) create mode 100644 docs/images/df.svg diff --git a/build.gradle.kts b/build.gradle.kts index dd159c0b..c0415544 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("org.jetbrains.dokka") version "0.10.1" } -val dataforgeVersion by extra("0.1.8-dev-4") +val dataforgeVersion by extra("0.1.8-dev-5") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt index a94b3667..4b409a08 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt @@ -183,6 +183,11 @@ fun Configurable.stringList(vararg strings: String, key: Name? = null): ReadWrit it?.value?.stringList ?: emptyList() } +fun Configurable.stringListOrNull(vararg strings: String, key: Name? = null): ReadWriteProperty?> = + item(listOf(*strings), key) { + it?.value?.stringList + } + fun Configurable.numberList(vararg numbers: Number, key: Name? = null): ReadWriteProperty> = item(listOf(*numbers), key) { item -> item?.value?.list?.map { it.number } ?: emptyList() diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt index dbb90cf2..db80b6fa 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt @@ -1,28 +1,23 @@ package hep.dataforge.tables -import kotlin.reflect.KClass - /** - * @param C bottom type for all columns in the table + * @param T bottom type for all columns in the table */ -class ColumnTable(override val columns: Collection>) : Table { +class ColumnTable(override val columns: Collection>) : Table { private val rowsNum = columns.first().size init { require(columns.all { it.size == rowsNum }) { "All columns must be of the same size" } } - override val rows: List> + override val rows: List> get() = (0 until rowsNum).map { VirtualRow(this, it) } - override fun getValue(row: Int, column: String, type: KClass): T? { - val value = columns[column]?.get(row) - return type.cast(value) - } + override fun getValue(row: Int, column: String): T? = columns[column]?.get(row) } -internal class VirtualRow(val table: Table, val index: Int) : Row { - override fun getValue(column: String, type: KClass): T? = table.getValue(index, column, type) +internal class VirtualRow(val table: Table, val index: Int) : Row { + override fun getValue(column: String): T? = table.getValue(index, column) // override fun get(columnHeader: ColumnHeader): T? { // return table.co[columnHeader][index] diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt deleted file mode 100644 index 04c7d3a4..00000000 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt +++ /dev/null @@ -1,10 +0,0 @@ -package hep.dataforge.tables - -import kotlin.reflect.KClass - -inline class MapRow(val values: Map) : Row { - override fun getValue(column: String, type: KClass): T? { - val value = values[column] - return type.cast(value) - } -} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt index 8a9b06bc..4b93a13d 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt @@ -1,8 +1,5 @@ package hep.dataforge.tables -import hep.dataforge.meta.Meta -import kotlin.reflect.KClass - /** * Mutable table with a fixed size, but dynamic columns */ @@ -14,10 +11,7 @@ class MutableColumnTable(val size: Int) : Table { VirtualRow(this, it) } - override fun getValue(row: Int, column: String, type: KClass): T? { - val value = columns[column]?.get(row) - return type.cast(value) - } + override fun getValue(row: Int, column: String): C? = columns[column]?.get(row) /** * Add a fixed column to the end of the table @@ -35,27 +29,3 @@ class MutableColumnTable(val size: Int) : Table { _columns.add(index, column) } } - -class MapColumn( - val source: Column, - override val type: KClass, - override val name: String, - override val meta: Meta = source.meta, - val mapper: (T?) -> R? -) : Column { - override val size: Int get() = source.size - - override fun get(index: Int): R? = mapper(source[index]) -} - -class CachedMapColumn( - val source: Column, - override val type: KClass, - override val name: String, - override val meta: Meta = source.meta, - val mapper: (T?) -> R? -) : Column { - override val size: Int get() = source.size - private val values: HashMap = HashMap() - override fun get(index: Int): R? = values.getOrPut(index) { mapper(source[index]) } -} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt index aa2d9abf..a59108a6 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt @@ -9,7 +9,7 @@ class MutableTable( override val header: MutableList> ) : RowTable(rows, header) { - fun column(name: String, type: KClass, meta: Meta): ColumnHeader { + fun column(name: String, type: KClass, meta: Meta): ColumnHeader { val column = SimpleColumnHeader(name, type, meta) header.add(column) return column diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt index c565d9cd..aa3449fb 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt @@ -4,6 +4,10 @@ import hep.dataforge.meta.Meta import kotlinx.coroutines.flow.toList import kotlin.reflect.KClass +inline class MapRow(val values: Map) : Row { + override fun getValue(column: String): C? = values[column] +} + internal class RowTableColumn(val table: Table, val header: ColumnHeader) : Column { override val name: String get() = header.name override val type: KClass get() = header.type @@ -14,8 +18,7 @@ internal class RowTableColumn(val table: Table, val header: C } open class RowTable(override val rows: List>, override val header: List>) : Table { - override fun getValue(row: Int, column: String, type: KClass): T? = - rows[row].getValue(column, type) + override fun getValue(row: Int, column: String): C? = rows[row].getValue(column) override val columns: List> get() = header.map { RowTableColumn(this, it) } } diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt index bba90d38..4ff9f3cb 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt @@ -3,32 +3,23 @@ package hep.dataforge.tables import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import kotlin.reflect.KClass - -//TODO to be removed in 1.3.70 -@Suppress("UNCHECKED_CAST") -internal fun KClass.cast(value: Any?): T? { - return when { - value == null -> null - !isInstance(value) -> error("Expected type is $this, but found ${value::class}") - else -> value as T - } -} +import kotlin.reflect.cast /** * Finite or infinite row set. Rows are produced in a lazy suspendable [Flow]. * Each row must contain at least all the fields mentioned in [header]. */ -interface Rows { - val header: TableHeader - fun rowFlow(): Flow> +interface Rows { + val header: TableHeader + fun rowFlow(): Flow> } -interface Table : Rows { - fun getValue(row: Int, column: String, type: KClass): T? - val columns: Collection> - override val header: TableHeader get() = columns.toList() - val rows: List> - override fun rowFlow(): Flow> = rows.asFlow() +interface Table : Rows { + fun getValue(row: Int, column: String): T? + val columns: Collection> + override val header: TableHeader get() = columns.toList() + val rows: List> + override fun rowFlow(): Flow> = rows.asFlow() /** * Apply typed query to this table and return lazy [Flow] of resulting rows. The flow could be empty. @@ -40,14 +31,18 @@ interface Table : Rows { } } -operator fun Collection>.get(name: String): Column<*>? = find { it.name == name } +fun Table.getValue(row: Int, column: String, type: KClass): T? = + type.cast(getValue(row, column)) + +operator fun Collection>.get(name: String): Column? = find { it.name == name } inline operator fun Table.get(row: Int, column: String): T? = getValue(row, column, T::class) -operator fun Table.get(row: Int, column: ColumnHeader): T? = getValue(row, column.name, column.type) +operator fun Table.get(row: Int, column: ColumnHeader): T? = + getValue(row, column.name, column.type) -interface Column : ColumnHeader { +interface Column : ColumnHeader { val size: Int operator fun get(index: Int): T? } @@ -60,9 +55,11 @@ operator fun Column.iterator() = iterator { } } -interface Row { - fun getValue(column: String, type: KClass): T? +interface Row { + fun getValue(column: String): T? } -inline operator fun Row.get(column: String): T? = getValue(column, T::class) +fun Row.getValue(column: String, type: KClass): T? = type.cast(getValue(column)) + +inline operator fun Row<*>.get(column: String): T? = T::class.cast(getValue(column)) operator fun Row.get(column: ColumnHeader): T? = getValue(column.name, column.type) \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/TransformationColumn.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/TransformationColumn.kt new file mode 100644 index 00000000..3c4fa2b4 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/TransformationColumn.kt @@ -0,0 +1,60 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import kotlin.reflect.KClass + +/** + * A virtual column obtained by transforming Given row to a single value + */ +class TransformationColumn( + val table: Table, + override val type: KClass, + override val name: String, + override val meta: Meta, + val mapper: (Row) -> R? +) : Column { + override val size: Int get() = table.rows.size + + override fun get(index: Int): R? = mapper(table.rows[index]) +} + +/** + * A virtual column obtained via transformation of single column with caching results on call (evaluation is lazy). + * + * Calls are not thread safe + */ +class CachedTransformationColumn( + val table: Table, + override val type: KClass, + override val name: String, + override val meta: Meta, + val mapper: (Row) -> R? +) : Column { + override val size: Int get() = table.rows.size + private val values: HashMap = HashMap() + override fun get(index: Int): R? = values.getOrPut(index) { mapper(table.rows[index]) } +} + +/** + * Create a virtual column from a given column + */ +inline fun Table.mapRows( + name: String, + meta: Meta = Meta.EMPTY, + cache: Boolean = false, + noinline mapper: (Row) -> R? +): Column = if (cache) { + CachedTransformationColumn(this, R::class, name, meta, mapper) +} else { + TransformationColumn(this, R::class, name, meta, mapper) +} + +fun Table.mapRowsToDouble(name: String, meta: Meta = Meta.EMPTY, block: (Row) -> Double): RealColumn { + val data = DoubleArray(rows.size) { block(rows[it]) } + return RealColumn(name, data, meta) +} + +fun Table.mapRowsToInt(name: String, meta: Meta = Meta.EMPTY, block: (Row) -> Int): IntColumn { + val data = IntArray(rows.size) { block(rows[it]) } + return IntColumn(name, data, meta) +} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt index 00715844..293031f8 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt @@ -13,7 +13,6 @@ import kotlinx.io.text.forEachUtf8Line import kotlinx.io.text.readUtf8Line import kotlinx.io.text.readUtf8StringUntilDelimiter import kotlinx.io.text.writeUtf8String -import kotlin.reflect.KClass /** * Read a lin as a fixed width [Row] @@ -92,9 +91,9 @@ class TextTable( } } - override fun getValue(row: Int, column: String, type: KClass): T? { + override fun getValue(row: Int, column: String): Value? { val offset = index[row] - return type.cast(readAt(offset)[column]) + return readAt(offset)[column] } companion object { diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/numericColumns.kt similarity index 83% rename from dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt rename to dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/numericColumns.kt index 1bd8d6a3..7f80b082 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/numericColumns.kt @@ -3,9 +3,8 @@ package hep.dataforge.tables import hep.dataforge.meta.Meta import kotlin.reflect.KClass -//interface NumberColumn : Column -data class RealColumn( +class RealColumn( override val name: String, val data: DoubleArray, override val meta: Meta = Meta.EMPTY @@ -44,12 +43,7 @@ data class RealColumn( } } -fun Column.map(meta: Meta = this.meta, block: (T?) -> Double): RealColumn { - val data = DoubleArray(size) { block(get(it)) } - return RealColumn(name, data, meta) -} - -data class IntColumn( +class IntColumn( override val name: String, val data: IntArray, override val meta: Meta = Meta.EMPTY @@ -86,9 +80,4 @@ data class IntColumn( noinline metaBuilder: ColumnScheme.() -> Unit ): IntColumn = IntColumn(name, data, ColumnScheme(metaBuilder).toMeta()) } -} - -fun Column.map(meta: Meta = this.meta, block: (T?) -> Int): IntColumn { - val data = IntArray(size) { block(get(it)) } - return IntColumn(name, data, meta) } \ No newline at end of file diff --git a/docs/images/df.svg b/docs/images/df.svg new file mode 100644 index 00000000..f977bd86 --- /dev/null +++ b/docs/images/df.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + +