前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >FunDA(4)- 数据流内容控制:Stream data element control

FunDA(4)- 数据流内容控制:Stream data element control

作者头像
用户1150956
发布2018-01-05 11:08:51
8660
发布2018-01-05 11:08:51
举报

    上节我们探讨了通过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

代码语言:javascript
复制
 1 package com.bayakala.funda {
 2 
 3   import fs2._
 4 
 5   package object fdapipes {
 6     //数据行类型
 7     trait FDAROW
 8 
 9     //数据处理管道
10     type FDAPipeLine[ROW] = Stream[Task, ROW]
11     //数据作业节点
12     type FDAWorkNode[ROW] = Pipe[Task, ROW, ROW]
13     //数据管道开关阀门,从此处获得管道内数据
14     type FDAValve[ROW] = Handle[Task, ROW]
15     //管道连接器
16     type FDAPipeJoint[ROW] = Pull[Task, ROW, Unit]
17 
18     //作业类型
19     type FDATask[ROW] = ROW => Option[List[ROW]]
20 
21   }
22 
23 }

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

流动控制方法:FDAValves.scala

代码语言:javascript
复制
 1 package com.bayakala.funda.fdapipes
 2 import fs2._
 3 object FDAValves {  //流动控制方法
 4 //跳过本行(不向下游发送)
 5   def fda_skip[ROW] = Some(List[ROW]())
 6 //将本行发送至下游连接管道
 7   def fda_next[ROW](r: ROW) = Some(List[ROW](r))
 8 //终止流动
 9   def fda_break = None
10 
11 }

数据发送方法:FDAPipes.scala

代码语言:javascript
复制
1 package com.bayakala.funda.fdapipes
2 import fs2._
3 object FDAJoints {  //数据发送方法
4 //write rows down the pipeline
5   def fda_pushRow[ROW](row: ROW) = Pull.output1(row)
6   def fda_pushRows[ROW](rows: List[ROW]) = Pull.output(Chunk.seq(rows))
7 }

作业节点工作方法:

代码语言:javascript
复制
 1 package com.bayakala.funda.fdapipes
 2 import FDAJoints._
 3 object FDANodes { //作业节点工作方法
 4  def fda_execUserTask[ROW](task: FDATask[ROW]): FDAWorkNode[ROW] = {
 5    def go: FDAValve[ROW] => FDAPipeJoint[ROW] = h => {
 6      h.receive1Option {
 7        case Some((r, h)) => task(r) match {
 8          case Some(xr) => xr match {
 9            case Nil => go(h)
10            case _ => fda_pushRows(xr) >> go(h)
11          }
12          case None => fda_halt
13        }
14        case None => fda_halt
15      }
16    }
17    in => in.pull(go)
18  }
19 
20 }

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

代码语言:javascript
复制
 1 package com.bayakala.funda.fdapipes.examples
 2 import fs2._
 3 import com.bayakala.funda.fdapipes._
 4 import FDANodes._
 5 import FDAValves._
 6 import Helpers._
 7 object Example1 extends App {
 8 
 9 
10   case class Employee(id: Int, name: String, age: Int, salary: BigDecimal) extends FDAROW
11 // test data set
12   val r1 = Employee(1, "John", 23, 100.00)
13   val r2 = Employee(2, "Peter", 25,100.00)
14   val r3 = Employee(3, "Kay", 35,100.00)
15   val r4 = Employee(4, "Cain", 45,100.00)
16   val r5 = Employee(5, "Catty", 35,100.00)
17   val r6 = Employee(6, "Little", 19,80.00)

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

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

代码语言:javascript
复制
1 package com.bayakala.funda.fdapipes.examples
2 import com.bayakala.funda.fdapipes._
3 import fs2.Task
4 object Helpers {
5   def log[ROW](prompt: String): FDAWorkNode[ROW] =
6     _.evalMap {row => Task.delay{ println(s"$prompt> $row"); row }}
7 }

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

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

代码语言:javascript
复制
 1 // 20 - 30岁加10%, 30岁> 加20%,其它加 5%
 2   def raisePay: FDATask[FDAROW] = row => {
 3     row match {
 4       case emp: Employee => {
 5         val cur = emp.age match {
 6           case a if ((a >= 20) && (a < 30)) => emp.copy(salary = emp.salary * 1.10)
 7           case a if ((a >= 30)) => emp.copy(salary = emp.salary * 1.20)
 8           case _ => emp.copy(salary = emp.salary * 1.05)
 9         }
10         fda_next(cur)
11       }
12       case _ => fda_skip
13     }
14   }

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

代码语言:javascript
复制
  Stream(r1,r2,r3,r4,r5,r6)
    .through(log("加薪前>"))
      .through(fda_execUserTask[FDAROW](raisePay))
      .through(log("加薪后>"))
    .run.unsafeRun
-----
运算结果:
加薪前>> Employee(1,John,23,100.0)
加薪后>> Employee(1,John,23,110.00)
加薪前>> Employee(2,Peter,25,100.0)
加薪后>> Employee(2,Peter,25,110.00)
加薪前>> Employee(3,Kay,35,100.0)
加薪后>> Employee(3,Kay,35,120.00)
加薪前>> Employee(4,Cain,45,100.0)
加薪后>> Employee(4,Cain,45,120.00)
加薪前>> Employee(5,Catty,35,100.0)
加薪后>> Employee(5,Catty,35,120.00)
加薪前>> Employee(6,Little,19,80.0)
加薪后>> Employee(6,Little,19,84.000)

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

代码语言:javascript
复制
  // 筛选40岁以上员工
  def filter40: FDATask[FDAROW] = row => {
    row match {
      case emp: Employee => {
        if (emp.age > 40)
          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(1,John,23,100.0)
年龄>> Employee(2,Peter,25,100.0)
年龄>> Employee(3,Kay,35,100.0)
年龄>> Employee(4,Cain,45,100.0)
合格>> Employee(4,Cain,45,100.0)
年龄>> Employee(5,Catty,35,100.0)
年龄>> Employee(6,Little,19,80.0)
-

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

代码语言:javascript
复制
 1   // 浏览至第一个30岁以上员工,跳出
 2   def stopOn30: FDATask[Employee] = emp => {
 3         if (emp.age > 30)
 4           fda_break
 5         else
 6           Some(List(emp))
 7   }
 8   println("---------")
 9   Stream(r1,r2,r3,r4,r5,r6)
10     .through(log("当前员工>"))
11     .through(fda_execUserTask[Employee](stopOn30))
12     .through(log("选入名单>"))
13     .run.unsafeRun
14 ---
15 运算结果:
16 当前员工>> Employee(1,John,23,100.0)
17 选入名单>> Employee(1,John,23,100.0)
18 当前员工>> Employee(2,Peter,25,100.0)
19 选入名单>> Employee(2,Peter,25,100.0)
20 当前员工>> Employee(3,Kay,35,100.0)

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

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

代码语言:javascript
复制
  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(1,John,23,100.0)
加薪后>> Employee(1,John,23,110.00)
年龄>> Employee(1,John,23,110.00)
加薪前>> Employee(2,Peter,25,100.0)
加薪后>> Employee(2,Peter,25,110.00)
年龄>> Employee(2,Peter,25,110.00)
加薪前>> Employee(3,Kay,35,100.0)
加薪后>> Employee(3,Kay,35,120.00)
年龄>> Employee(3,Kay,35,120.00)
加薪前>> Employee(4,Cain,45,100.0)
加薪后>> Employee(4,Cain,45,120.00)
年龄>> Employee(4,Cain,45,120.00)
合格>> Employee(4,Cain,45,120.00)
加薪前>> Employee(5,Catty,35,100.0)
加薪后>> Employee(5,Catty,35,120.00)
年龄>> Employee(5,Catty,35,120.00)
加薪前>> Employee(6,Little,19,80.0)
加薪后>> Employee(6,Little,19,84.000)
年龄>> Employee(6,Little,19,84.000)

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

代码语言:javascript
复制
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(1, "John", 23, 100.00)
  val r2 = Employee(2, "Peter", 25,100.00)
  val r3 = Employee(3, "Kay", 35,100.00)
  val r4 = Employee(4, "Cain", 45,100.00)
  val r5 = Employee(5, "Catty", 35,100.00)
  val r6 = Employee(6, "Little", 19,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 >= 20) && (a < 30)) => emp.copy(salary = emp.salary * 1.10)
          case a if ((a >= 30)) => 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 > 40)
          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 > 30)
          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

}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-01-19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档