上节我们探讨了通过scalaz-stream-fs2来驱动一套数据处理流程,用fs2的Pipe类型来实现对数据流的逐行操作。本篇讨论准备在上节讨论的基础上对数据流的流动和元素操作进行优化完善。如数据流动中增加诸如next、skip、eof功能、内容控制中增加对行元素的append、insert、update、remove等操作方法。但是经过一番对fs2的再次解读,发现这些操作模式并不像我所想象那样的方式,实际上用fs2来实现数据行控制可能会更加简单和直接。这是因为与传统数据库行浏览方式不同的是fs2是一种拖式流(pull-model stream),它的数据行集合是一种泛函不可变集合。每一行一旦读取就等于直接消耗了断(consumed),所以只支持一种向前逐行读取模式。如果形象地描述的话,我们习惯的所谓数据集浏览可能是下面这样的场景:

读取一行数据 >>> (使用或更新行字段值)>>> 向下游发送新的一行数据。只有停止发送动作才代表终止运算。完成对上游的所有行数据读取并不代表终止操作,因为我们还可以不断向下游发送自定义产生的数据行。

我们用fs2模拟一套数据流管道FDAPipeLine,管道中间有不定数量的作业节点FDAWorkNode。作业方式包括从管道上游截取一个数据元素、对其进行处理、然后选择是否向下游的管道接口(FDAPipeJoint)发送。下面是这套模拟的类型:fdapipes/package.scala

 package com.bayakala.funda {

   import fs2._

   package object fdapipes {
//数据行类型
trait FDAROW //数据处理管道
type FDAPipeLine[ROW] = Stream[Task, ROW]
//数据作业节点
type FDAWorkNode[ROW] = Pipe[Task, ROW, ROW]
//数据管道开关阀门,从此处获得管道内数据
type FDAValve[ROW] = Handle[Task, ROW]
//管道连接器
type FDAPipeJoint[ROW] = Pull[Task, ROW, Unit] //作业类型
type FDATask[ROW] = ROW => Option[List[ROW]] } }

注意这个FDAROW类型:这是一种泛类型,因为在管道中流动的数据可能有多重类型,如数据行和QueryAction行。

流动控制方法:FDAValves.scala

 package com.bayakala.funda.fdapipes
import fs2._
object FDAValves { //流动控制方法
//跳过本行(不向下游发送)
def fda_skip[ROW] = Some(List[ROW]())
//将本行发送至下游连接管道
def fda_next[ROW](r: ROW) = Some(List[ROW](r))
//终止流动
def fda_break = None }

数据发送方法:FDAPipes.scala

 package com.bayakala.funda.fdapipes
import fs2._
object FDAJoints { //数据发送方法
//write rows down the pipeline
def fda_pushRow[ROW](row: ROW) = Pull.output1(row)
def fda_pushRows[ROW](rows: List[ROW]) = Pull.output(Chunk.seq(rows))
}

作业节点工作方法:

 package com.bayakala.funda.fdapipes
import FDAJoints._
object FDANodes { //作业节点工作方法
def fda_execUserTask[ROW](task: FDATask[ROW]): FDAWorkNode[ROW] = {
def go: FDAValve[ROW] => FDAPipeJoint[ROW] = h => {
h.receive1Option {
case Some((r, h)) => task(r) match {
case Some(xr) => xr match {
case Nil => go(h)
case _ => fda_pushRows(xr) >> go(h)
}
case None => fda_halt
}
case None => fda_halt
}
}
in => in.pull(go)
} }

下面我们就示范这个工具库的具体使用方法:examples/Example1.scala
设置示范环境:

 package com.bayakala.funda.fdapipes.examples
import fs2._
import com.bayakala.funda.fdapipes._
import FDANodes._
import FDAValves._
import Helpers._
object Example1 extends App { case class Employee(id: Int, name: String, age: Int, salary: BigDecimal) extends FDAROW
// test data set
val r1 = Employee(, "John", , 100.00)
val r2 = Employee(, "Peter", ,100.00)
val r3 = Employee(, "Kay", ,100.00)
val r4 = Employee(, "Cain", ,100.00)
val r5 = Employee(, "Catty", ,100.00)
val r6 = Employee(, "Little", ,80.00)

注意Employee是一种行类型,因为它extends FDAROW。

我们再写一个跟踪显示当前流动数据行的函数:examples/Helpers.scala

 package com.bayakala.funda.fdapipes.examples
import com.bayakala.funda.fdapipes._
import fs2.Task
object Helpers {
def log[ROW](prompt: String): FDAWorkNode[ROW] =
_.evalMap {row => Task.delay{ println(s"$prompt> $row"); row }}
}

下面我们就用几个有不同要求的例子来示范流动控制和数据处理功能,这些例子就是给最终用户的标准编程示范版本,然后由用户照版编写:

1、根据每条数据状态逐行进行处理:

 // 20 - 30岁加10%, 30岁> 加20%,其它加 5%
def raisePay: FDATask[FDAROW] = row => {
row match {
case emp: Employee => {
val cur = emp.age match {
case a if ((a >= ) && (a < )) => emp.copy(salary = emp.salary * 1.10)
case a if ((a >= )) => emp.copy(salary = emp.salary * 1.20)
case _ => emp.copy(salary = emp.salary * 1.05)
}
fda_next(cur)
}
case _ => fda_skip
}
}

用户提供的功能函数类型必须是FDATask[FDAROW]。类型参数FDAROW代表数据行通用类型。如果用户指定了FDATask[Employee]函数类型,那么必须保证管道中流动的数据行只有Employee一种类型。完成对当前行数据的处理后用fda_next(emp)把它发送到下一节连接管道。我们用下面的组合函数来进行运算:

  Stream(r1,r2,r3,r4,r5,r6)
.through(log("加薪前>"))
.through(fda_execUserTask[FDAROW](raisePay))
.through(log("加薪后>"))
.run.unsafeRun
-----
运算结果:
加薪前>> Employee(,John,,100.0)
加薪后>> Employee(,John,,110.00)
加薪前>> Employee(,Peter,,100.0)
加薪后>> Employee(,Peter,,110.00)
加薪前>> Employee(,Kay,,100.0)
加薪后>> Employee(,Kay,,120.00)
加薪前>> Employee(,Cain,,100.0)
加薪后>> Employee(,Cain,,120.00)
加薪前>> Employee(,Catty,,100.0)
加薪后>> Employee(,Catty,,120.00)
加薪前>> Employee(,Little,,80.0)
加薪后>> Employee(,Little,,84.000)

2、在一组数据行内根据每条数据状态进行筛选:

  // 筛选40岁以上员工
def filter40: FDATask[FDAROW] = row => {
row match {
case emp: Employee => {
if (emp.age > )
Some(List(emp))
else fda_skip[Employee]
}
case _ => fda_break
}
}
println("---------")
Stream(r1,r2,r3,r4,r5,r6)
.through(log("年龄>"))
.through(fda_execUserTask[FDAROW](filter40))
.through(log("合格>"))
.run.unsafeRun
---
运算结果:
年龄>> Employee(,John,,100.0)
年龄>> Employee(,Peter,,100.0)
年龄>> Employee(,Kay,,100.0)
年龄>> Employee(,Cain,,100.0)
合格>> Employee(,Cain,,100.0)
年龄>> Employee(,Catty,,100.0)
年龄>> Employee(,Little,,80.0)
-

3、根据当前数据行状态终止作业:

   // 浏览至第一个30岁以上员工,跳出
def stopOn30: FDATask[Employee] = emp => {
if (emp.age > )
fda_break
else
Some(List(emp))
}
println("---------")
Stream(r1,r2,r3,r4,r5,r6)
.through(log("当前员工>"))
.through(fda_execUserTask[Employee](stopOn30))
.through(log("选入名单>"))
.run.unsafeRun
---
运算结果:
当前员工>> Employee(,John,,100.0)
选入名单>> Employee(,John,,100.0)
当前员工>> Employee(,Peter,,100.0)
选入名单>> Employee(,Peter,,100.0)
当前员工>> Employee(,Kay,,100.0)

在这个例子里用户指定了行类型统一为Employee。

我们还可以把多个功能串接起来。像下面这样把1和2两个功能连起来:

  Stream(r1,r2,r3,r4,r5,r6)
.through(log("加薪前>"))
.through(fda_execUserTask[FDAROW](raisePay))
.through(log("加薪后>"))
.through(log("年龄>"))
.through(fda_execUserTask[FDAROW](filter40))
.through(log("合格>"))
.run.unsafeRun
---
运算结果:
加薪前>> Employee(,John,,100.0)
加薪后>> Employee(,John,,110.00)
年龄>> Employee(,John,,110.00)
加薪前>> Employee(,Peter,,100.0)
加薪后>> Employee(,Peter,,110.00)
年龄>> Employee(,Peter,,110.00)
加薪前>> Employee(,Kay,,100.0)
加薪后>> Employee(,Kay,,120.00)
年龄>> Employee(,Kay,,120.00)
加薪前>> Employee(,Cain,,100.0)
加薪后>> Employee(,Cain,,120.00)
年龄>> Employee(,Cain,,120.00)
合格>> Employee(,Cain,,120.00)
加薪前>> Employee(,Catty,,100.0)
加薪后>> Employee(,Catty,,120.00)
年龄>> Employee(,Catty,,120.00)
加薪前>> Employee(,Little,,80.0)
加薪后>> Employee(,Little,,84.000)
年龄>> Employee(,Little,,84.000)

下面我把完整的示范代码提供给大家:

package com.bayakala.funda.fdapipes.examples
import fs2._
import com.bayakala.funda.fdapipes._
import FDANodes._
import FDAValves._
import Helpers._
object Example1 extends App { case class Employee(id: Int, name: String, age: Int, salary: BigDecimal) extends FDAROW
// test data set
val r1 = Employee(, "John", , 100.00)
val r2 = Employee(, "Peter", ,100.00)
val r3 = Employee(, "Kay", ,100.00)
val r4 = Employee(, "Cain", ,100.00)
val r5 = Employee(, "Catty", ,100.00)
val r6 = Employee(, "Little", ,80.00) // 20 - 30岁加10%, 30岁> 加20%,其它加 5%
def raisePay: FDATask[FDAROW] = row => {
row match {
case emp: Employee => {
val cur = emp.age match {
case a if ((a >= ) && (a < )) => emp.copy(salary = emp.salary * 1.10)
case a if ((a >= )) => emp.copy(salary = emp.salary * 1.20)
case _ => emp.copy(salary = emp.salary * 1.05)
}
fda_next(cur)
}
case _ => fda_skip
}
} Stream(r1,r2,r3,r4,r5,r6)
.through(log("加薪前>"))
.through(fda_execUserTask[FDAROW](raisePay))
.through(log("加薪后>"))
.run.unsafeRun // 筛选40岁以上员工
def filter40: FDATask[FDAROW] = row => {
row match {
case emp: Employee => {
if (emp.age > )
Some(List(emp))
else fda_skip[Employee]
}
case _ => fda_break
}
}
println("---------")
Stream(r1,r2,r3,r4,r5,r6)
.through(log("年龄>"))
.through(fda_execUserTask[FDAROW](filter40))
.through(log("合格>"))
.run.unsafeRun // 浏览至第一个30岁以上员工,跳出
def stopOn30: FDATask[Employee] = emp => {
if (emp.age > )
fda_break
else
Some(List(emp))
}
println("---------")
Stream(r1,r2,r3,r4,r5,r6)
.through(log("当前员工>"))
.through(fda_execUserTask[Employee](stopOn30))
.through(log("选入名单>"))
.run.unsafeRun println("---------")
Stream(r1,r2,r3,r4,r5,r6)
.through(log("加薪前>"))
.through(fda_execUserTask[FDAROW](raisePay))
.through(log("加薪后>"))
.through(log("年龄>"))
.through(fda_execUserTask[FDAROW](filter40))
.through(log("合格>"))
.run.unsafeRun }

FunDA(4)- 数据流内容控制:Stream data element control的更多相关文章

  1. 学习笔记20151211——AXI4 STREAM DATA FIFO

    AXI4 STREAM DATA FIFO是输入输出接口均为AXIS接口的数据缓存器,和其他fifo一样是先进先出形式.可以在跨时钟域的应用中用于数据缓冲,避免亚稳态出现.支持数据的分割和数据拼接.在 ...

  2. 数据访问模式:数据并发控制(Data Concurrency Control)

    1.数据并发控制(Data Concurrency Control)简介 数据并发控制(Data Concurrency Control)是用来处理在同一时刻对被持久化的业务对象进行多次修改的系统.当 ...

  3. 错误解决:SharePoint Designer 2010编辑后,出现数据源控件未能执行插入命令,data source control failed to execute the insert command

    打了SharePoint 2010 最新的SP 2的补丁,但是使用SharePoint Designer 2010 定义任何一个列表的“插入视图”时,总是出现标题那样的错误: 数据源控件未能执行插入命 ...

  4. 创建数据表,自定义data element, field等。

    参考:https://wenku.baidu.com/view/253ddbfaa5e9856a561260da.html 一:创建域. 使用T-CODE 11 搜索 数据操作系统. 选择domain ...

  5. Putting Apache Kafka To Use: A Practical Guide to Building a Stream Data Platform-part 1

    转自: http://www.confluent.io/blog/stream-data-platform-1/ These days you hear a lot about "strea ...

  6. 关于$.data(element,key,value)与ele.data.(key,value)的区别

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  7. AXI4 STREAM DATA FIFO

    参考:http://www.xilinx.com/support/documentation/ip_documentation/axis_infrastructure_ip_suite/v1_1/pg ...

  8. 泛函编程(12)-数据流-Stream

    在前面的章节中我们介绍了List,也讨论了List的数据结构和操作函数.List这个东西从外表看上去挺美,但在现实中使用起来却可能很不实在.为什么?有两方面:其一,我们可以发现所有List的操作都是在 ...

  9. Putting Apache Kafka To Use: A Practical Guide to Building a Stream Data Platform-part 2

    转自: http://confluent.io/blog/stream-data-platform-2          http://www.infoq.com/cn/news/2015/03/ap ...

随机推荐

  1. SSH登录到远程linux机器并执行命令

    一. 1.JSch是Java Secure Channel的缩写.JSch是一个SSH2的纯Java实现.它允许你连接到一个SSH服务器,并且可以使用端口转发,X11转发,文件传输等,当然你也可以集成 ...

  2. Python.__getattr__Vs__getattribute__

    __getattr__ Vs __getattribute__ class Fish(object): def __getattr__(self, key): if key == 'color': p ...

  3. iOS.ObjC.Basic-Knowledge

    1. ObjC的基础 2. ObjC2.0中的编译指令 3. ObjC Runtime 4. ObjC Object Model 5. ObjC的新语法 6. FQA 1. ObjC的基础 2. Ob ...

  4. 品味性能之道<五>:SQL分析工具

    一.SQL语句到底是怎么执行的? 想了解SQL语句到底是怎么执行的,那就需要进行SQL语句执行计划分析. 那什么是SQL语句执行计划呢? 就是Oracle服务器执行SQL语句的过程.例如确定是否使用索 ...

  5. Linux操作系统Vim代码Tab自动补全配置

    function! CleverTab() , col( ) =~ '^\s*$' return "\<Tab>" else return "\<C-N ...

  6. jquery ui中的dialog,官网上经典的例子

    jquery ui中的dialog,官网上经典的例子   jquery ui中dialog和easy ui中的dialog很像,但是最近用到的时候全然没有印象,一段时间不用就忘记了,这篇随笔介绍一下这 ...

  7. sqlserver中set IDENTITY_INSERT on 和 off 的设置方法

    sqlserver中set IDENTITY_INSERT on 和 off 的设置方法: 执行插入数据库插入数据时报了以下错误,我明明没有给主键set值但还是报错 解决方法如下: qlserver ...

  8. 2018.07.10 NOIP模拟 sort(单调队列)

    Sort 题目背景 SOURCE:NOIP2016-RZZ-4 T1 题目描述 给你一个长度为 n 的排列,小W每次可以选择一个数,做以下操作: 不断把这个数与它右边的数交换. 当它右边没有数,或它右 ...

  9. 简单的Java,Python,C,C++

    Java 语言 //package main //注意不要添加包名称,否则会报错. import java.io.*; import java.util.*; cin.hasNext(); cin.h ...

  10. 大文件webuploader的基本使用

    webuploader的简单使用 需要的文件   自备  百度很多 webuploader.js  uploader.swf  jQuery <!DOCTYPE html> <htm ...