函数式编程:如何高效简洁地对数据查询与变换
案例1
案例一,代码摘抄来自一企业培训材料,主要代码逻辑是打印每课成绩,并找出学生非F
级别课程统计平均分数:
class CourseGrade {public String title;public char grade;
}public class ReportCard {public String studentName;public ArrayList<CourseGrade> cliens;public void printReport() {System.out.println("Report card for " + studentName);System.out.println("------------------------");System.out.println("Course Title Grade");Iterator<CourseGrade> grades = cliens.iterator();CourseGrade grade;double avg = 0.0d;while (grades.hasNext()) {grade = grades.next();System.out.println(grade.title + " " + grade.grade);if (!(grade.grade == 'F')) {avg = avg + grade.grade - 64;}}avg = avg / cliens.size();System.out.println("------------------------");System.out.println("Grade Point Average = " + avg);}
}
上面的代码有哪些问题呢:
- 成员变量采用public,缺少数据封装性
- 没有判断cliens是否为空,可能除以0值。注:假定它不会为空,另外逻辑可能有问题,为什么统计总分是非F课程,除数却是所有课程Size,先忽略这个问题
- avg这个变量多个用途,即是总分,又是平均分
- cliens变量名难以理解
!(grade.grade == 'F')
有点反直觉- while循环干了两件事,打印每课的成绩,也统计了分数
培训材料并未给标准解题,尝试优化一下代码,采用Java8的Stream来简化计算过程,并对代码进行了分段:
public void printReport2() {System.out.println("Report card for " + studentName);System.out.println("------------------------");System.out.println("Course Title Grade");cliens.forEach(it -> System.out.println(it.title + " " + it.grade));double total = cliens.stream().filter(it -> it.grade != 'F').mapToDouble(it -> it.grade - 64).sum();System.out.println("------------------------");System.out.println("Grade Point Average = " + total / cliens.size());}
进一步优化,把各类打印抽取各自函数:
private void printHeader() {System.out.println("Report card for " + studentName);System.out.println("------------------------"); }private void printGrade() {System.out.println("Course Title Grade");cliens.forEach(it -> System.out.println(it.title + " " + it.grade));}private void printAverage() {double total = cliens.stream().filter(it -> it.grade != 'F').mapToDouble(it -> it.grade - 64).sum();System.out.println("------------------------");System.out.println("Grade Point Average = " + total / cliens.size());}public void printReport3() {printHeader();printGrade();printAverage();}
注:如果只算非F的平均分,可以一行搞定:
double avg = cliens.stream().filter(it -> it.grade != 'F').mapToDouble(it -> it.grade - 64).average().orElse(0.0d);
案例二:再看一段代码:
List<Integer> tanscationsIds = transcations.parallelStream().filter(it -> it.getType() == Transcation.GROCERY).sorted(comparing(Transcation::getValue).resersed()).map(Transcation::getId).collect(Collectors::toList());
代码非常清晰:
- 过滤出类型为GROCERY的交易记录
- 按其value值进行倒排序
- 各自取其Id字段
- 输出Id列表
这看起来是不是像这样一条SQL语句:
select t.id from tanscations t where t.type == 'GROCERY' order by t.value desc
1.1 背后的知识
目前Java8已广泛使用,对于Stream与Lambda应习以为常了,而不再是一种炫技。网上也有非常多的教程,若有同学还不熟悉他们的用法,可以多找找材料熟悉一下。
Stream正如其名,像一条数据生产流水线,逐步叠加中间操作(算法和计算),把数据源转换为另一个数据集。
笔者很早以前学过C#,接触过LINQ(Language Integrated Query),它比Java的Stream和Lambda用法更为清晰简洁,先给个简单示例:
var result = db.ProScheme.OrderByDescending(p => p.rpId).Where(p => p.rpId > 10).ToList();
LINQ为数据查询而生,可以算是DSL(Domain Specific Language)了,背后也是函数式编程(FP)一套理念,先记住其中两点:
- Monad 是一种设计模式,表示将一个运算过程,通过函数拆解成互相连接的多个步骤
- Lambda表达式 是一个匿名函数,Lambda表达式基于数学中的λ演算得名
FP还有其它的特性:模式匹配,柯里化,偏函数,闭包,尾递归等。对FP感觉兴趣的同学不妨找找材料学习一下。
现在的主流语言,都引入一些FP特性来提升语言在数据上的表达能力。
C++11引入Lambda表达式,并提供<algorithm>
,<functional>
两个基础库,一个简单示例:
int foo[] = { 10, 20, 5, 15, 25 };
std::sort(foo, foo+5, [](int a,int b){return a > b;});
Python提供functools库来简化一些函数式编程(还是相当的弱),一个简单示例:
foo = ["A", "a", "b", "B"]
sorted(foo, key=functools.cmp_to_key(locale.strcoll))
2 函数式编程
当然,面向对象语言中增加lambda这类特征不能就称为函数式编程了,大部分只不过是语法糖。是采用什么编程范式不在于语言的语法,而是在于思维方式。
面向对象编程(OOP)在过去20多年非常成功,而函数式编程(FP)也不断地发展,他们相生相息,各自解决不同的场景问题:
- 面向对象可以理解为是对数据的抽象,比如把一个事物抽象成一个对象,关注的是数据。
- 函数式编程是一种过程抽象的思维,就是对当前的动作去进行抽象,关注的是动作。
现实业务需求往往体现为业务活动,它是面向过程的,即先输入数据源,在一定条件下,进行一系列的交互,再输出结果。那面向过程与函数式的的区别是什么:
- 面向过程可以理解是把做事情的动作进行分解多个步骤,所以有if/while这类语法支撑,走不同的分支步骤。
- 函数式相比面向过程式,它更加地强调执行结果而非执行过程,利用若干个简单的执行单元让计算结果不断渐近,逐层推导复杂的运算,而不是像面向过程设计出复杂的执行过程,所以纯函数式编程语言中不需要if/while这类语法,而是模式匹配,递归调用等。
面向对象的编程通过封装可变的部分来构造能够让人读懂的代码,函数式编程则是通过最大程度地减少可变的部分来构造出可让人读懂的代码。
我们从Java的Stream实现也看到函数式的另一个特点:
- 函数不维护任何状态,上下文的数据是不变的,传入的参数据处理完成之后再扔出来。
结合上面的理解,我们可以先把世界事物通过OOP抽象为对象,再把事物间的联系与交互通过FP抽象为执行单元,这种结合或许是对业务活动的实现一种较好的解决方式。
3 避免单一范式
一提到编程范式,很容易联想到宗教的虔诚,每种宗教所表达信条都有一定合理性,但如果一直只遵循一种教条,可能也被让自己痛苦不堪。编程范式也是如此,正如Java在1.8之前是纯面向对象式,你就会觉得它非常繁琐。也如Erlang是纯函数式,你就会发现有时简单的逻辑处理会非常复杂。
近些年来,由于数据分析、科学计算和并行计算的兴起,让人认识到函数式编程解决数据领域的魅力,它也越来越受欢迎。在这些领域,程序往往比较容易用数据表达式来表达,采用函数式可以用很少代码来实现。
现实的业务软件,很多的逻辑其实也是对数据的处理,最简单是对数据的CURD,以及数据的组合、过滤与查询。所以函数式编程在许多语言中都得到支持,提升了对数据处理的表达能力。
了解新的编程范式在适当的时候使用它们,这会使你事半功倍。无论什么编程范式,他们都是工具,在你的工具箱中,可能有锤子,螺丝刀…,这个工具在什么时候使用,取决待解决的问题。
4 结语
本文的案例只是一个引子,主要是想给你带来函数式编程的一些理念,函数式给我们解决业务问题提供了另一种思维方式:如何高效简洁地对数据查询与变换。许多语言都支持函数式一些能力,需要我们不断地学习,在合理的场景下使用他们。
点击关注,第一时间了解华为云新鲜技术~
本文分享自华为云社区《飞哥讲代码16:函数式让数据处理更简洁》,原文作者:华为云专家。
函数式编程:如何高效简洁地对数据查询与变换相关推荐
- 函数式编程——做到并发,不可变数据修改就只能复制后修改返回
函数式编程 from:https://coolshell.cn/articles/10822.html 当我们说起函数式编程来说,我们会看到如下函数式编程的长相: 函数式编程的三大特性: immuta ...
- (Java数据库编程基础操作)Statement实现数据查询操作
Statement实现数据查询操作 在整个数据库的开发过程之中数据的查询操作是最复杂的,因为查询会牵扯到各种复杂查询的管理,例如:多表查询.子查询.统计查询.集合操作等等,但是从现代的开发来讲,对于数 ...
- pthon_函数式编程与面向对象编程的比较
函数式编程作为结构化编程的一种,正在受到越来越多的重视.工程中不在只是面向对象编程,更多的人尝试着开始使用函数式编程来解决软件工程中遇到的问题. 什么是函数式编程?在维基百科中给出了详细的定义,函数式 ...
- 函数式编程与面向对象编程的优劣
函数式编程作为结构化编程的一种,正在受到越来越多的重视.工程中不在只是面向对象编程,更多的人尝试着开始使用函数式编程来解决软件工程中遇到的问题. 什么是函数式编程?在维基百科中给出了详细的定义,函数式 ...
- 函数式编程与面向对象编程的比较
函数式编程作为结构化编程的一种,正在受到越来越多的重视.工程中不在只是面向对象编程,更多的人尝试着开始使用函数式编程来解决软件工程中遇到的问题. 什么是函数式编程?在维基百科中给出了详细的定义,函数式 ...
- 玩转 JavaScript 面试:何为函数式编程?
函数式编程在 JavaScript 领域着实已经成为一个热门话题.就在几年前,很多 JavaScript 程序员甚至都不知道啥是函数式编程,但是就在近三年里我看到过的每一个大型应用的代码库中都包含了函 ...
- JavaScript函数式编程入门-计算器应用
目录 预备知识 函数式编程 模块化 计算器示例 功能分析 创建index.html 计算过程显示栏实现 创建计算过程显示栏 输出栏组件实现 创建输出栏组件 键盘组件实现 创建键盘组件 完成计算器 函数 ...
- JavaScript 什么是函数式编程
原文: Master the JavaScript Interview: What is Functional Programming? 译文: 什么是函数式编程?什么是命令式?声明式 - 一起学习可 ...
- java函数式编程入门
以前写过一篇java8的流操作,人们都说流操作是函数式编程,但函数式编程是什么呢? 什么是函数式编程 什么是函数式编程?它是一种编程范式,即一切都是数学函数.函数式编程语言里也可以有对象,但通常这些对 ...
最新文章
- SVN 两种存储格式(BDB和FSFS)区别
- 语言可以直接访问位元元址_OOP语言中FBC问题对应用框架的影响
- springboot启动不打印日志信息_SpringBoot日志操作【全局异常捕获消息处理--日志控制台输出+日志文件记录】...
- Web程序中将Cell华表的修改 保存到数据库或服务器端文件夹内
- 能从入门到精通的 Docker 学习指南
- Mysql-不常用sql语句笔记与不常见错误
- classNotFound异常的一个原因
- Tuxera NTFS 2021 for Mac(NTFS磁盘格式读写工具)
- JavaScript词法分析
- 米聊PC版体验评测: 简约过头的即时聊天工具
- 可靠性试验(环境试验、机械试验、HALT试验等)
- 用html代码制作一个表单页面,HTML网页表单制作详细讲解
- 机器学习——时间序列预测方法
- QCY T3 蓝牙耳机连接电脑 声音断续卡顿
- 计算机网络安装,计算机网络系统安装操作指南.pdf
- canvas绘制图形API(一)
- provide和inject 用法
- midjourney使用方法保姆级注册教程AI绘画工具关键词
- SQL Server Management Studio
- Fastly释Lucet原生WebAssembly编译程序和Runtime
热门文章
- 机器人J中WPR_机器人经颅磁刺激(Robotized TMS)技术知多少?
- pcie16x能插1x的卡嘛?_5G手机插4G卡,在没有5G信号的地方,跟4G手机的网速一样吗?...
- python的except之后还运行吗_python except异常处理之后不退出,如何解决异常继续执行...
- oracle数据泵导入导出_【软件】R语言数据导入与导出
- Batch批量替换hosts
- 压缩感知——SP(subspace pursuit)重构算法前言翻译
- NCrawler爬取中文网页时乱码问题的解决方法
- 【SPOJ】2713 Can you answer these queries IV
- leetcode 1227 python
- 触摸屏开发_Microchip推出新型电容触摸式控制器,加速汽车触摸屏EMI认证