This commit is contained in:
Alexander Nozik 2018-12-16 19:37:11 +03:00
parent 08c208cc46
commit 81d7fbcffa
2 changed files with 101 additions and 19 deletions

View File

@ -0,0 +1,59 @@
package hep.dataforge.data
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
/**
* A simple data transformation on a data node
*/
interface Action<in T : Any, out R : Any> {
/**
* Transform the data in the node, producing a new node. By default it is assumed that all calculations are lazy
* so not actual computation is started at this moment
*/
operator fun invoke(node: DataNode<T>, meta: Meta): DataNode<R>
/**
* Terminal action is the one that could not be invoked lazily and requires some kind of blocking computation to invoke
*/
val isTerminal: Boolean get() = false
}
/**
* Action composition. The result is terminal if one of parts is terminal
*/
infix fun <T : Any, I : Any, R : Any> Action<T, I>.then(action: Action<I, R>): Action<T, R> {
return object : Action<T, R> {
override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> {
return action(this@then.invoke(node, meta), meta)
}
override val isTerminal: Boolean
get() = this@then.isTerminal || action.isTerminal
}
}
/**
* An action that performs the same transformation on each of input data nodes. Null results are ignored.
*/
class PipeAction<in T : Any, out R : Any>(val transform: (Name, Data<T>, Meta) -> Data<R>?) : Action<T, R> {
override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> = DataNode.build {
node.asSequence().forEach { (name, data) ->
val res = transform(name, data, meta)
if (res != null) {
set(name, res)
}
}
}
companion object {
/**
* A simple pipe that performs transformation on the data and copies input meta into the output
*/
fun <T : Any, R : Any> simple(transform: suspend (Name, T, Meta) -> R) = PipeAction { name, data: Data<T>, meta ->
val goal = data.goal.pipe { transform(name, it, meta) }
return@PipeAction Data.of(goal, data.meta)
}
}
}

View File

@ -7,34 +7,47 @@ import hep.dataforge.names.NameToken
import hep.dataforge.names.plus
import hep.dataforge.names.toName
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.KClass
/**
* A data element characterized by its meta
*/
interface Data<out T> : MetaRepr {
interface Data<out T : Any> : MetaRepr {
/**
* Type marker for the data. The type is known before the calculation takes place so it could be cheched.
*/
val type: KClass<out T>
/**
* Meta for the data
*/
val meta: Meta
/**
* Lazy data value
*/
val goal: Goal<T>
override fun toMeta(): Meta = meta
companion object {
fun <T> of(meta: Meta, goal: Goal<T>): Data<T> = DataImpl(meta, goal)
fun <T> of(name: String, meta: Meta, goal: Goal<T>): Data<T> = NamedData(name, of(meta, goal))
fun <T> static(context: CoroutineContext, meta: Meta, value: T): Data<T> = DataImpl(meta, Goal.static(context, value))
fun <T : Any> of(type: KClass<out T>, goal: Goal<T>, meta: Meta): Data<T> = DataImpl(type, goal, meta)
inline fun <reified T : Any> of(goal: Goal<T>, meta: Meta): Data<T> = of(T::class, goal, meta)
fun <T : Any> of(name: String, goal: Goal<T>, meta: Meta): Data<T> = NamedData(name, of(goal, meta))
fun <T : Any> static(context: CoroutineContext, value: T, meta: Meta): Data<T> = DataImpl(value::class, Goal.static(context, value), meta)
}
}
/**
* Generic Data implementation
*/
private class DataImpl<out T>(override val meta: Meta, override val goal: Goal<T>) : Data<T>
private class DataImpl<out T : Any>(override val type: KClass<out T>, override val goal: Goal<T>, override val meta: Meta) : Data<T>
class NamedData<out T>(val name: String, data: Data<T>) : Data<T> by data
class NamedData<out T : Any>(val name: String, data: Data<T>) : Data<T> by data
/**
* A tree-like data structure grouped into the node. All data inside the node must inherit its type
*/
interface DataNode<out T> {
interface DataNode<out T : Any> {
/**
* Get the specific data if it exists
*/
@ -53,17 +66,17 @@ interface DataNode<out T> {
operator fun iterator(): Iterator<Pair<Name, Data<T>>> = asSequence().iterator()
companion object {
fun <T> build(block: DataTreeBuilder<T>.() -> Unit) = DataTreeBuilder<T>().apply(block).build()
fun <T : Any> build(block: DataTreeBuilder<T>.() -> Unit) = DataTreeBuilder<T>().apply(block).build()
}
}
internal sealed class DataTreeItem<out T> {
class Node<out T>(val tree: DataTree<T>) : DataTreeItem<T>()
class Value<out T>(val value: Data<T>) : DataTreeItem<T>()
internal sealed class DataTreeItem<out T : Any> {
class Node<out T : Any>(val tree: DataTree<T>) : DataTreeItem<T>()
class Value<out T : Any>(val value: Data<T>) : DataTreeItem<T>()
}
class DataTree<out T> internal constructor(private val items: Map<NameToken, DataTreeItem<T>>) : DataNode<T> {
class DataTree<out T : Any> internal constructor(private val items: Map<NameToken, DataTreeItem<T>>) : DataNode<T> {
//TODO add node-level meta?
override fun get(name: Name): Data<T>? = when (name.length) {
@ -93,15 +106,15 @@ class DataTree<out T> internal constructor(private val items: Map<NameToken, Dat
}
}
private sealed class DataTreeBuilderItem<out T> {
class Node<T>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>()
class Value<T>(val value: Data<T>) : DataTreeBuilderItem<T>()
private sealed class DataTreeBuilderItem<out T : Any> {
class Node<T : Any>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>()
class Value<T : Any>(val value: Data<T>) : DataTreeBuilderItem<T>()
}
/**
* A builder for a DataTree.
*/
class DataTreeBuilder<T> {
class DataTreeBuilder<T : Any> {
private val map = HashMap<NameToken, DataTreeBuilderItem<T>>()
operator fun set(token: NameToken, node: DataTreeBuilder<T>) {
@ -116,7 +129,7 @@ class DataTreeBuilder<T> {
private fun buildNode(token: NameToken): DataTreeBuilder<T> {
return if (!map.containsKey(token)) {
DataTreeBuilder<T>().also { map.put(token, DataTreeBuilderItem.Node(it)) }
DataTreeBuilder<T>().also { map[token] = DataTreeBuilderItem.Node(it) }
} else {
(map[token] as? DataTreeBuilderItem.Node ?: error("The node with name $token is occupied by leaf")).tree
}
@ -177,11 +190,21 @@ class DataTreeBuilder<T> {
/**
* Generate a mutable builder from this node. Node content is not changed
*/
fun <T> DataNode<T>.builder(): DataTreeBuilder<T> = DataTreeBuilder<T>().apply {
fun <T : Any> DataNode<T>.builder(): DataTreeBuilder<T> = DataTreeBuilder<T>().apply {
asSequence().forEach { (name, data) -> this[name] = data }
}
/**
* Start computation for all goals in data node
*/
fun DataNode<*>.startAll() = asSequence().forEach { (_,data)->data.goal.start() }
fun DataNode<*>.startAll() = asSequence().forEach { (_, data) -> data.goal.start() }
fun <T : Any> DataNode<T>.filter(predicate: (Name, Data<T>) -> Boolean): DataNode<T> = DataNode.build {
asSequence().forEach {(name, data)->
if(predicate(name,data)){
this[name] = data
}
}
}
//fun <T : Any, R: T> DataNode<T>.filterIsInstance(type: KClass<R>): DataNode<R> = filter{_,data -> type.}