FunDA是一种函数式的编程工具,它所产生的程序是由许多功能单一的细小函数组合而成,这些函数就是用户自定义操作函数了。我们在前面曾经提过FunDA的运作原理模拟了数据流管道。流元素在管道流动的过程中被使用或者更新。在管道中流动的元素都必须继承FDAROW类型,可以细分成几个大类:

1、数据行(data-row):因为FunDA的数据行必须是强类型的,所以各种case class类型继承了FDAROW(extends FDAROW)之后最为适合

2、动作行(action-row):case class包嵌slick.DBIOAction的数据类型,如:FDAActionRow(slickQueryAction)

3、异常行(exception-row):case class包嵌Exception类型,是下面这样申明的:

case class FDAErrorRow(e: Exception) extends FDAROW

4、终止行(end-of-stream):数据流终止信号,用于通知下游节点已经没有流动元素了

FunDA自定义操作函数的主要目的是在某个流节点对流元素进行使用和处理。乍看好像直接用函数式编程中的map,flatMap函数都能达到同样的目标,如:

fdaStream.map(row => transformData(row)).map(action => runQueryAction(action))

但经过实验后发现标准流操作函数map,flatMap缺乏功能强大又可以灵活应用的流动操作,而这又是流式数据处理至关重要的一项功能。这就是为什么我们需要一套新的用户自定义函数了。

FunDA规范了一套标准的自定义函数操作流程,由一下几个步骤组成:

1、确定当前流元素类型

2、在该类型的框架内使用和变动流元素字段值

3、流动控制:控制元素向下游的流动

我们将在这篇讨论里示范各种形式和功能的自定义函数。承上篇的示范所产生的数据表AIRQM。这是一个直接导入cvs文件产生的数据表,所有字段都是String类型的。我们的示范就是把这个表里的字段属性转换成匹配的类型后生成一个新表AQMRPT,并把AIRQM里数据的字段值经过转换后并入新表。下面是这个表的结构:

  case class AQMRPTModel(rid: Long
, mid: Int
, state: String
, county: String
, year: Int
, value: Int
, total: Int
, valid: Boolean) extends FDAROW class AQMRPTTable(tag: Tag) extends Table[AQMRPTModel](tag, "AQMRPT") {
def rid = column[Long]("ROWID",O.AutoInc,O.PrimaryKey)
def mid = column[Int]("MEASUREID")
def state = column[String]("STATENAME",O.Length())
def county = column[String]("COUNTYNAME",O.Length())
def year = column[Int]("REPORTYEAR")
def value = column[Int]("VALUE")
def total = column[Int]("TOTAL")
def valid = column[Boolean]("VALID") def * = (rid,mid,state,county,year,value,total,valid) <> (AQMRPTModel.tupled, AQMRPTModel.unapply)
}
val AQMRPTQuery = TableQuery[AQMRPTTable]

注意我们用extends FDAROW把AQMRPTModel变成了强类型数据行类型,这是必须的。现在AQMRPTQuery就是这个新的数据表了。下面是这个表的创建和使用铺垫代码:

//drop original table schema
val futVectorTables = db.run(MTable.getTables) val futDropTable = futVectorTables.flatMap{ tables => {
val tableNames = tables.map(t => t.name.name)
if (tableNames.contains(AQMRPTQuery.baseTableRow.tableName))
db.run(AQMRPTQuery.schema.drop)
else Future()
}
}.andThen {
case Success(_) => println(s"Table ${AQMRPTQuery.baseTableRow.tableName} dropped successfully! ")
case Failure(e) => println(s"Failed to drop Table ${AQMRPTQuery.baseTableRow.tableName}, it may not exist! Error: ${e.getMessage}")
}
Await.ready(futDropTable,Duration.Inf) //create new table to refine AQMRawTable
val actionCreateTable = Models.AQMRPTQuery.schema.create
val futCreateTable = db.run(actionCreateTable).andThen {
case Success(_) => println("Table created successfully!")
case Failure(e) => println(s"Table may exist already! Error: ${e.getMessage}")
}
//would carry on even fail to create table
Await.ready(futCreateTable,Duration.Inf) //truncate data, only available in slick 3.2.1
val futTruncateTable = futVectorTables.flatMap{ tables => {
val tableNames = tables.map(t => t.name.name)
if (tableNames.contains(AQMRPTQuery.baseTableRow.tableName))
db.run(AQMRPTQuery.schema.truncate)
else Future()
}
}.andThen {
case Success(_) => println(s"Table ${AQMRPTQuery.baseTableRow.tableName} truncated successfully!")
case Failure(e) => println(s"Failed to truncate Table ${AQMRPTQuery.baseTableRow.tableName}! Error: ${e.getMessage}")
}
Await.ready(futDropTable,Duration.Inf)

通过上面这段铺垫代码(boiler-code)使我们保证获得一个空的AQMRPTQuery表。下一步我们把AQMRaw载入内存作为FunDA程序的一个数据源(source)来使用:

//load original table content
//original table strong-typed-row
case class AQMRaw(mid: String, state: String,
county: String, year: String, value: String) extends FDAROW
implicit def toAQMRaw(row: (String,String,String,String,String)) =
AQMRaw(row._1,row._2,row._3,row._4,row._5)
val streamLoader = FDAStreamLoader(slick.jdbc.H2Profile)(toAQMRaw _)
// val queryAQMRaw = for { r <- AQMRawQuery } yield (r.mid,r.state,r.county,r.year,r.value)
val queryAQMRaw = sql"""
SELECT MEASUREID,STATENAME,COUNTYNAME,REPORTYEAR,VALUE FROM AIRQM
""".as[(String,String,String,String,String)] val streamAQMRaw: FDAPipeLine[FDAROW] = streamLoader.fda_typedStream(queryAQMRaw)(db)(,)()

注意我们使用了slick的plain sql来示范创建这个数据源。AQMRaw类型是这个源的强类型数据行类型,所以又必须extends FDAROW。再就是类型转换函数toAQMRaw是implicit def的,这是一种确保转换函数存在的措施,提供给compiler在编译时使用(试试如果不用implicit def会怎样,仔细阅读compiler的错误提示)。我们把数据导入的流程分成下面几个步骤:

1、载入数据源 >>> 数据行类型转换:从AQMRowModel转成AQMRPTModel >>> 把新类型的数据行传给下游

2、把上游传来的数据行转换成动作行FDAActionRow(queryAction),然后把这个动作行传给下游

3、对上游传来的动作行进行运算

上面这三个大步骤代表三个功能单一,细化的用户自定义函数。我们先看看第一步:这是一个典型格式的自定义函数:

//filter out rows with inconvertible value strings and out of ranged year
def filterRows: FDAUserTask[FDAROW] = row => {
row match {
case r: AQMRaw => {
try {
val yr = r.year.toInt
val v = r.value.toInt
val vlu = if ( v > ) else v
val data = AQMRPTModel(,r.mid.toInt,r.state,r.county,yr,vlu,,true)
if ((yr > && yr < ))
fda_next(data) //this row ok. pass downstream
else
fda_skip //filter out this row
} catch {
case e: Exception =>
fda_next(AQMRPTModel(,r.mid.toInt,r.state,r.county,,,,false))
//pass a invalid row
}
}
case _ => fda_skip //wrong type, skip
}
}

下面我们把用户自定义函数filterRows与自定义函数的标准操作流程对应一下:

1、确定数据行类型:row match { case r: AQMRow => ??? 通过这段明确了数据行是AQMRow类型的

2、下面是数据行的内容的具体应用:

data = AQMRPTModel(,r.mid.toInt,r.state,r.county,yr,vlu,,true)

我们使用了AQMRaw的行字段r.??来构建AQMRPTModel数据行。

3、fda_next(???)把新构建的AQMRPTModel行传到下游

以上几步证明filterRows是按照自定义函数操作标准来运作的。

第二步是把新类型的数据行转换成一条动作行,然后传给下游。由下面这个用户自定义函数来实现:

//transform data to action for later execution
def toAction: FDAUserTask[FDAROW] = row => {
row match {
case r: AQMRPTModel =>
val queryAction = AQMRPTQuery += r
fda_next(FDAActionRow(queryAction))
case _ => fda_skip
}
}

toAction同样遵循自定义函数的操作标准。我们需要需要一个运算器来运算动作行:

//get a query runner and an action task
val actionRunner = FDAActionRunner(slick.jdbc.H2Profile)
def runActionRow: FDAUserTask[FDAROW] = action => {
action match {
case FDAActionRow(q) => actionRunner.fda_execAction(q)(db)
fda_skip
case _ => fda_skip
}
}

runActionRow在程序的最后一个节点,是个终点函数,不传送任何数据行到下游。把这三个函数组合成一个FunDA程序然后startRun:

/start the program
val streamAllTasks = streamAQMRaw.appendTask(filterRows)
.appendTask(toAction)
.appendTask(runActionRow) val streamToRun = streamAllTasks.onError { case e: Exception => println("Error:"+e.getMessage); fda_appendRow(FDAErrorRow(new Exception(e))) } streamToRun.startRun

注意在startRun之前我们可以对FunDA stream进行任何组合。运行startRun后检验数据库表清单里是否增加了AQMRPT表。

除了每行数据的独立应用外,很多时候我们都会对一组串联的数据行进行某种汇总操作(aggregation),比如清点行数、对行内某字段进行汇总计算等。FunDA提供了自定义汇总函数(user-defined-aggregation)来实现这个目的。下面是一个自定义汇总函数例子:

//user defined aggregation task
def aggregateValue: FDAAggrTask[Accu,FDAROW] = (accu,row) => {
row match {
case aqmr: AQMRPTModel =>
if (accu.state == "" || (aqmr.state == accu.state && aqmr.year == accu.year))
//same condition: inc count and add sum, pass no row downstream
(Accu(aqmr.state,aqmr.county,aqmr.year,accu.count+, accu.sumOfValue+aqmr.value),fda_skip)
else
//reset accumulator, create a new aggregated row and pass downstream
(Accu(aqmr.state,aqmr.county,aqmr.year,, aqmr.value)
,fda_next(AQMRPTModel(,,accu.state,accu.county,accu.year
,accu.count,accu.sumOfValue/accu.count,true)))
case FDANullRow =>
//last row encountered. create and pass new aggregated row
(Accu(accu.state,accu.county,accu.year,, )
,fda_next(AQMRPTModel(,,accu.state,accu.county,accu.year
,accu.count,accu.sumOfValue/accu.count,true)))
//incorrect row type, do nothing
case _ => (accu,fda_skip)
}
}

自定义汇总函数的款式是FDAAggrTask,如下定义:

  type FDAAggrTask[AGGR,ROW] = (AGGR,ROW) => (AGGR,Option[List[ROW]])

AGGR是个用户自定义类型,用来记录汇总当前状态。ROW类型代表数据行类型。自定义汇总函数aggregateValue的功能如下:

1、对AQMRPT表里的数据按statename,year进行汇总

2、产生一条新的汇总数据行并把它插入AQMRPT表里。

汇总函数就是一种状态函数,它的典型函数表现形式就是输入原状态,输出新状态。自定义汇总函数必须用aggregateTask来组合:

  aqmrStream.aggregateTask(Accu("","",,,),aggregateValue)
.appendTask(toAction)
.appendTask(runActionRow)
.startRun

Accu是个自定义case class。在调用startRun之前我们把初始状态Accused("","",0,0,0)传入aggregateTask。aqmrStream是个数据源,它的铺垫代码如下:

//aggregate-task demo: get count and sum of value for each state and year
val orderedAQMRPT = AQMRPTQuery.sortBy(r => (r.state,r.year))
//TableElementType conversion. must declare implicit
implicit def toAQMRPT(row: AQMRPTTable#TableElementType) =
AQMRPTModel(row.rid,row.mid,row.state,row.county,row.year,row.value,row.total,row.valid)
val aqmrStreamLoader = FDAStreamLoader(slick.jdbc.H2Profile)(toAQMRPT _)
val aqmrStream: FDAPipeLine[FDAROW] = aqmrStreamLoader.fda_typedStream(orderedAQMRPT.result)(db)(,)()

注意我们这次使用了slick TableQuery原始行类型AQMRPTTable#TableElementType来进行强类型转换。

本次示范的源代码如下:

import slick.jdbc.meta._
import com.bayakala.funda._
import api._
import scala.language.implicitConversions
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.util.{Failure, Success}
import slick.jdbc.H2Profile.api._
import Models._ object UserDefinedTasks extends App { val db = Database.forConfig("h2db") //drop original table schema
val futVectorTables = db.run(MTable.getTables) val futDropTable = futVectorTables.flatMap{ tables => {
val tableNames = tables.map(t => t.name.name)
if (tableNames.contains(AQMRPTQuery.baseTableRow.tableName))
db.run(AQMRPTQuery.schema.drop)
else Future()
}
}.andThen {
case Success(_) => println(s"Table ${AQMRPTQuery.baseTableRow.tableName} dropped successfully! ")
case Failure(e) => println(s"Failed to drop Table ${AQMRPTQuery.baseTableRow.tableName}, it may not exist! Error: ${e.getMessage}")
}
Await.ready(futDropTable,Duration.Inf) //create new table to refine AQMRawTable
val actionCreateTable = Models.AQMRPTQuery.schema.create
val futCreateTable = db.run(actionCreateTable).andThen {
case Success(_) => println("Table created successfully!")
case Failure(e) => println(s"Table may exist already! Error: ${e.getMessage}")
}
//would carry on even fail to create table
Await.ready(futCreateTable,Duration.Inf) //truncate data, only available in slick 3.2.1
val futTruncateTable = futVectorTables.flatMap{ tables => {
val tableNames = tables.map(t => t.name.name)
if (tableNames.contains(AQMRPTQuery.baseTableRow.tableName))
db.run(AQMRPTQuery.schema.truncate)
else Future()
}
}.andThen {
case Success(_) => println(s"Table ${AQMRPTQuery.baseTableRow.tableName} truncated successfully!")
case Failure(e) => println(s"Failed to truncate Table ${AQMRPTQuery.baseTableRow.tableName}! Error: ${e.getMessage}")
}
Await.ready(futDropTable,Duration.Inf) //load original table content
//original table strong-typed-row
case class AQMRaw(mid: String, state: String,
county: String, year: String, value: String) extends FDAROW
implicit def toAQMRaw(row: (String,String,String,String,String)) =
AQMRaw(row._1,row._2,row._3,row._4,row._5)
val streamLoader = FDAStreamLoader(slick.jdbc.H2Profile)(toAQMRaw _)
// val queryAQMRaw = for { r <- AQMRawQuery } yield (r.mid,r.state,r.county,r.year,r.value)
val queryAQMRaw = sql"""
SELECT MEASUREID,STATENAME,COUNTYNAME,REPORTYEAR,VALUE FROM AIRQM
""".as[(String,String,String,String,String)] val streamAQMRaw: FDAPipeLine[FDAROW] = streamLoader.fda_typedStream(queryAQMRaw)(db)(,)() //filter out rows with inconvertible value strings and out of ranged value and year
def filterRows: FDAUserTask[FDAROW] = row => {
row match {
case r: AQMRaw => {
try {
val yr = r.year.toInt
val v = r.value.toInt
val vlu = if ( v > ) else v
val data = AQMRPTModel(,r.mid.toInt,r.state,r.county,yr,vlu,,true)
if ((yr > && yr < ))
fda_next(data) //this row ok. pass downstream
else
fda_skip //filter out this row
} catch {
case e: Exception =>
fda_next(AQMRPTModel(,r.mid.toInt,r.state,r.county,,,,false))
//pass a invalid row
}
}
case _ => fda_skip //wrong type, skip
}
} //transform data to action for later execution
def toAction: FDAUserTask[FDAROW] = row => {
row match {
case r: AQMRPTModel =>
val queryAction = AQMRPTQuery += r
fda_next(FDAActionRow(queryAction))
case _ => fda_skip
}
} //get a query runner and an action task
val actionRunner = FDAActionRunner(slick.jdbc.H2Profile)
def runActionRow: FDAUserTask[FDAROW] = action => {
action match {
case FDAActionRow(q) => actionRunner.fda_execAction(q)(db)
fda_skip
case _ => fda_skip
}
} //start the program
val streamAllTasks = streamAQMRaw.appendTask(filterRows)
.appendTask(toAction)
.appendTask(runActionRow) val streamToRun = streamAllTasks.onError { case e: Exception => println("Error:"+e.getMessage); fda_appendRow(FDAErrorRow(new Exception(e))) } streamToRun.startRun //aggregate-task demo: get count and sum of value for each state and year
val orderedAQMRPT = AQMRPTQuery.sortBy(r => (r.state,r.year))
//TableElementType conversion. must declare implicit
implicit def toAQMRPT(row: AQMRPTTable#TableElementType) =
AQMRPTModel(row.rid,row.mid,row.state,row.county,row.year,row.value,row.total,row.valid)
val aqmrStreamLoader = FDAStreamLoader(slick.jdbc.H2Profile)(toAQMRPT _)
val aqmrStream: FDAPipeLine[FDAROW] = aqmrStreamLoader.fda_typedStream(orderedAQMRPT.result)(db)(,)()
//user defined aggregator type.
case class Accu(state: String, county: String, year: Int, count: Int, sumOfValue: Int)
//user defined aggregation task
def aggregateValue: FDAAggrTask[Accu,FDAROW] = (accu,row) => {
row match {
case aqmr: AQMRPTModel =>
if (accu.state == "" || (aqmr.state == accu.state && aqmr.year == accu.year))
//same condition: inc count and add sum, pass no row downstream
(Accu(aqmr.state,aqmr.county,aqmr.year,accu.count+, accu.sumOfValue+aqmr.value),fda_skip)
else
//reset accumulator, create a new aggregated row and pass downstream
(Accu(aqmr.state,aqmr.county,aqmr.year,, aqmr.value)
,fda_next(AQMRPTModel(,,accu.state,accu.county,accu.year
,accu.count,accu.sumOfValue/accu.count,true)))
case FDANullRow =>
//last row encountered. create and pass new aggregated row
(Accu(accu.state,accu.county,accu.year,, )
,fda_next(AQMRPTModel(,,accu.state,accu.county,accu.year
,accu.count,accu.sumOfValue/accu.count,true)))
//incorrect row type, do nothing
case _ => (accu,fda_skip)
}
} aqmrStream.aggregateTask(Accu("","",,,),aggregateValue)
.appendTask(toAction)
.appendTask(runActionRow)
.startRun }

FunDA(13)- 示范:用户自定义操作函数 - user defined tasks的更多相关文章

  1. dedecms功能性函数封装(XSS过滤、编码、浏览器XSS hack、字符操作函数)

    dedecms虽然有诸多漏洞,但不可否认确实是一个很不错的内容管理系统(cms),其他也不乏很多功能实用性的函数,以下就部分列举,持续更新,不作过多说明.使用时需部分修改,你懂的 1.XSS过滤. f ...

  2. PHP常用的文件操作函数集锦

    以下是个人总结的PHP文件操作函数.当然,这只是部分,还有很多,我没有列出来. 一 .解析路径: 1 获得文件名:basename();给出一个包含有指向一个文件的全路径的字符串,本函数返回基本的文件 ...

  3. Spark SQL 用户自定义函数UDF、用户自定义聚合函数UDAF 教程(Java踩坑教学版)

    在Spark中,也支持Hive中的自定义函数.自定义函数大致可以分为三种: UDF(User-Defined-Function),即最基本的自定义函数,类似to_char,to_date等 UDAF( ...

  4. Delphi文件操作函数

    文件是同一种类型元素的有序集合,是内存与外设之间传输数据的渠道.文件的本质是一个数据流,所有的文件实际上是一串二进制序列.文件管理包括:1.文件操作.2.目录操作.3.驱动器操作.三部分. 1.常见文 ...

  5. Node.js文件系统、路径的操作函数

    Node.js文件系统.路径的操作函数 目录 Node.js文件系统.路径的操作函数 1.读取文件readFile函数 2.写文件 3.以追加方式写文件 4.打开文件 5.读文件,读取打开的文件内容到 ...

  6. signal函数、sigaction函数及信号集(sigemptyset,sigaddset)操作函数

    信号是与一定的进程相联系的.也就是说,一个进程可以决定在进程中对哪些信号进行什 么样的处理.例如,一个进程可以忽略某些信号而只处理其他一些信号:另外,一个进程还可以选择如何处理信号.总之,这些总与特定 ...

  7. JS封装cookie操作函数实例(设置、读取、删除)

    本文实例讲述了JS封装cookie操作函数.分享给大家供大家参考,具体如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ...

  8. 字符串操作函数<string.h>相关函数strcpy,strcat,等源码。

    首先说一下源码到底在哪里找. 我们在文件中包含<cstring>时,如果点击右键打开文档, 会打开cstring,我们会发现路径为: D:\Program Files\visual stu ...

  9. 总结文件操作函数-文件夹(三)-C语言

    获取.改变当前文件夹: 原型为: #include <unistd.h>   //头文件 char *getcwd(char *buf, size_t size); //获取当前文件夹.相 ...

随机推荐

  1. Netty 系列目录

    Netty 系列目录 二 Netty 源码分析(4.1.20) 1.1 Netty 源码(一)Netty 组件简介 2.1 Netty 源码(一)服务端启动 2.2 Netty 源码(二)客户端启动 ...

  2. 2018.07.17 CQOI2017 余数求和(整除分块)

    洛谷传送门 bzoj传送门 这道题要用到学习莫比乌斯反演时掌握的整除分块算法,也就是对于一个数n" role="presentation" style="pos ...

  3. slice、substring、substr

    slice() 定义和用法 slice() 方法可从已有的数组中返回选定的元素. string.slice(start, end)提取一个字符串 string.substring(start, end ...

  4. modelsim读写TXT文件

    //open the file Initial Begin step_file = $fopen("F:/Company/Src/txt/step.v","r" ...

  5. Ansible 笔记 (3) - 编写 playbook

    playbook 相当于多个命令的编排组合然后一起运行,类似写脚本.在学习 playbook 之前需要了解 yaml 格式. 编写playbook的步骤: 定义主机与用户 编写任务列表 执行 play ...

  6. POP介绍与使用实践(快速上手动画)[转]

    前言 动画在APP开发过程中 大家多多少少都会接触到 而且随着ios7的扁平化风格启用之后 越来越多的APP开始尝试加入各种绚丽的动画交互效果以增加APP的用户体验(当然 还是以国外的APP居多) 有 ...

  7. 【翻译】JavaScript框架的最终指南

    翻译原文链接 我的翻译小站 紧跟JavaScript框架的脚步是一个挑战.现在有太多的框架,几乎一个月就会出来一个新的.那么如何知道到底哪一个比较合适你的项目呢?它们分别有什么优点和缺点呢?你要如何开 ...

  8. (欧拉公式 很水) Coprimes -- sgu -- 1002

    链接: http://vj.acmclub.cn/contest/view.action?cid=168#problem/B Coprimes 时限:250MS     内存:4096KB     6 ...

  9. HDU1518 Square(DFS) 2016-07-24 15:08 49人阅读 评论(0) 收藏

    Square Problem Description Given a set of sticks of various lengths, is it possible to join them end ...

  10. 关于android4.3 bluetooth4.0的那些事儿

    马年伊始,刚刚上班的一个星期,公司里没什么事儿可做,只是听说马上可能要做蓝牙的项目.之前也做过关于软硬件通讯之类的项目:android 串口通讯,android usb 转串口通讯. 可是蓝牙这块还真 ...