热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

通过实例学习Either树和模式匹配

前言在这一期的文章中,我将继续介绍Either,使用它构建树形结构,该结构允许我模拟Scala的模式匹配来构建遍历方法。在Java中使用泛型数据

前言

在这一期的文章中,我将继续介绍 Either,使用它构建树形结构,该结构允许我模拟 Scala 的模式匹配来构建遍历方法。

在 Java 中使用泛型数据,Either 会成为接收两种类型之一的单一数据结构,这两种类型保存在 left 和 right 部分中。

在上一期文章的罗马数字解析器示例中,Either 保存了 Exception(左侧)或 Integer(右侧),如图 1 所示:

通过实例学习Either 树和模式匹配

图 1. Either 抽象保存了两种类型的其中之一

在本示例中,Either以如下的方式被填充:

Either result = RomanNumeralParser.parseNumber("XLII");

Scala 模式匹配

Scala 的众多出色功能之一就是能够使用 模式匹配 进行调度(参阅 参考资料)。与描述相比,演示更简单一些,因此我们会考虑清单 1 中的函数,将数字分数转换为字母分数:

清单 1. 使用 Scala 模式匹配根据分数调度字母分数

val VALID_GRADES = Set("A", "B", "C", "D", "F")
def letterGrade(value: Any) : String = value match {
case x:Int if (90 to 100).contains(x) => "A"
case x:Int if (80 to 90).contains(x) => "B"
case x:Int if (70 to 80).contains(x) => "C"
case x:Int if (60 to 70).contains(x) => "D"
case x:Int if (0 to 60).contains(x) => "F"
case x:String if VALID_GRADES(x.toUpperCase) => x.toUpperCase
}
printf("Amy scores %d and receives %s\n", 91, letterGrade(91))
printf("Bob scores %d and receives %s\n", 72, letterGrade(72))
printf("Sam never showed for class, scored %d, and received %s\n", 
44, letterGrade(44))
printf("Roy transfered and already had %s, which translated as %s\n",
"B", letterGrade("B"))

在 清单 1 中,函数的整个正文由应用于 value 的 match 构成。对于每个选项,模式防护 允许我根据除参数类型以外的条件筛选匹配内容。这种语法的优势是一个干净的选项分区,而不是一系列复杂的 if 语句。

模式匹配与 Scala 的 case 类一同工作,该类是具有特殊属性的类 (包括执行模式匹配的能力),以消除决策序列。考虑匹配颜色组合,如清单 2 所示:

清单 2. 在 Scala 中匹配 case 类

class Color(val red:Int, val green:Int, val blue:Int)
case class Red(r:Int) extends Color(r, 0, 0)
case class Green(g:Int) extends Color(0, g, 0)
case class Blue(b:Int) extends Color(0, 0, b)
def printColor(c:Color) = c match {
case Red(v) => println("Red: " + v)
case Green(v) => println("Green: " + v)
case Blue(v) => println("Blue: " + v)
case col:Color => {
print("R: " + col.red + ", ")
print("G: " + col.green + ", ")
println("B: " + col.blue)
}
case null => println("invalid color")
}

在 清单 2 中,我创建了一个基本 Color 类,然后与 case 类一样,创建了一个特殊的单一颜色版本。当确定将哪种颜色传递给函数时,我使用了 match,根据所有可用选项进行模式匹配,这些可用选项中包括最后一个 case,它将处理 null。

Java 没有提供模式匹配,因此它无法复制 Scala 的创建清晰可读的调度代码的能力。但是,通过结合使用泛型数据结构和众所周知的数据结构,可以实现更加接近的能力,这又将我带回了 Either。

Either 树

可以建模一个具有三个抽象的树形数据结构,如表 1 所示:

Empty 单元中不包含任何值
Leaf 单元中拥有一个特殊数据类型值
Node 指向其他 叶 或 节点

但是为了方便起见,我将在本例中使用来自 Functional Java 框架的一个类。从概念上讲,Either 抽象扩展到了所需的方面。例如,您可以考虑声明 Either>,这将创建一个三部分的数据结构,如图 2 所示:

通过实例学习Either 树和模式匹配

图 2. Either> 的数据结构

执行了三个树抽象的 Either 实现之后,我定义了树,如清单 3 所示:

清单 3. 基于 Either 的树

import fj.data.Either;
import static fj.data.Either.left;
import static fj.data.Either.right;
public abstract class Tree {
private Tree() {}
public abstract Either> toEither();
public static final class Empty extends Tree {
public Either> toEither() {
return left(this);
}
public Empty() {}
}
public static final class Leaf extends Tree {
public final int n;
public Either> toEither() {
return right(Either.left(this));
}
public Leaf(int n) { this.n = n; }
}
public static final class Node extends Tree {
public final Tree left;
public final Tree right;
public Either> toEither() {
return right(Either.right(this));
}
public Node(Tree left, Tree right) {
this.left = left;
this.right = right;
}
}
}

清单 3 中的抽象 Tree 类定义了三个 final 具体类:Empty、Leaf 和 Node。从内部讲,Tree 类使用 3 个插槽的 Either,如 图 2 所示,实现这样一种规则,即最左侧的插槽总是保存 Empty,中间插槽保存 Leaf,而最右侧的插槽保存 Node。方法是:请求每个类都实现 toEither() 方法,返回该类型相应的 “插槽”。从传统计算机科学方面讲,数据结构中的每个 “单元” 都是一个 union,旨在保存任意给定时间三种可能类型的其中一种类型。

考虑到此树形结构和其内部结构基于 > 的事实,我可以通过模拟模式匹配来访问树中的每个元素。

树遍历的模式匹配

Scala 的模式匹配鼓励您思考离散情况。Functional Java 的 Either 实现中的 left() 和 right() 方法都实现了 Iterable 接口;这允许我编写支持模式匹配的代码来确定树的深度,如清单 4 所示:

清单 4. 使用类似模式匹配的语法检查树的深度

static public int depth(Tree t) {
for (Empty e : t.toEither().left())
return 0;
for (Either ln: t.toEither().right()) {
for (Leaf leaf : ln.left())
return 1;
for (Node node : ln.right())
return 1 + max(depth(node.left), depth(node.right));
}
throw new RuntimeException("Inexhaustible pattern match on tree");
}

清单 4 中的 depth() 方法是一个递归深度查找函数。因为我的树基于一个具体的数据结构(>),所以我可以将每个 “插槽” 视为一个具体情况。如果单元为空,则树枝没有深度。如果单元为叶,则将它视为树级别。如果单元为节点,那么我会知道应该以递归方式搜索左侧和右侧,然后添加 1 进行另一次递归。

我还可以使用相同的模式匹配语法来执行树的递归搜索,如清单 5 所示:

清单 5. 在树中确定是否存在元素

static public boolean inTree(Tree t, int value) {
for (Empty e : t.toEither().left())
return false;
for (Either ln: t.toEither().right()) {
for (Leaf leaf : ln.left())
return value == leaf.n;
for (Node node : ln.right())
return inTree(node.left, value) | inTree(node.right, value);
}
return false;
}

与之前一样,我在数据结构中为每个可能的 “插槽” 指定一个 return 值。如果遇到一个空单元,则会返回 false;我的搜索会失败。对于叶,我会检查传递的值,如果它们匹配则返回 true。否则,在遇到节点时,我会遍历树,使用 |(非短路的 or 运算符)来组合返回的布尔值。

要查看实际的树创建和搜索,请考虑清单 6 中的单元测试:

清单 6. 测试树可搜索性

@Test
public void more_elaborate_searchp_test() {
Tree t = new Node(new Node(new Node(new Node(
new Node(new Leaf(4),new Empty()), 
new Leaf(12)), new Leaf(55)), 
new Empty()), new Leaf(4));
assertTrue(inTree(t, 55));
assertTrue(inTree(t, 4));
assertTrue(inTree(t, 12));
assertFalse(inTree(t, 42));
}

在 清单 6 中,我构建了树,然后调查了是否存在元素。inTree() 方法返回 true,如果其中一个叶等于搜索值,并且 true 传播了递归调用堆栈,这是因为 | ("or") 运算符,如 清单 5 所示。

清单 5 中的示例确定了元素是否出现于树中。更复杂的版本还会检查出现的次数,如清单 7 所示:

清单 7. 查找在树中出现的次数

static public int occurrencesIn(Tree t, int value) {
for (Empty e: t.toEither().left())
return 0;
for (Either ln: t.toEither().right()) {
for (Leaf leaf : ln.left())
if (value == leaf.n) return 1;
for (Node node : ln.right())
return occurrencesIn(node.left, value) + occurrencesIn(node.right, value);
}
return 0;
}

在 清单 7 中,我为每个匹配的叶返回了 1,这使我可以计算树中每个数字出现的次数。

清单 8 展示了复杂树中 depth()、inTree() 和 occurrencesIn() 的测试:

清单 8. 在复杂树中测试深度、存在状况和出现次数

@Test
public void multi_branch_tree_test() {
Tree t = new Node(new Node(new Node(new Leaf(4),
new Node(new Leaf(1), new Node(
new Node(new Node(new Node(
new Node(new Node(new Leaf(10), new Leaf(0)),
new Leaf(22)), new Node(new Node(
new Node(new Leaf(4), new Empty()),
new Leaf(101)), new Leaf(555))),
new Leaf(201)), new Leaf(1000)),
new Leaf(4)))),
new Leaf(12)), new Leaf(27));
assertEquals(12, depth(t));
assertTrue(inTree(t, 555));
assertEquals(3, occurrencesIn(t, 4));
}

由于我对树的内部结构应用了正则性,因此我可以在遍历期间分析树,方法是思考每种情况,如元素类型所示。该语法尽管不像完全成熟的 Scala 模式匹配一样强大,但是与 Scala 出乎意料的接近。

结束语

在这一期的文章中,我介绍了如何在树遍历期间,对启用了 Scala 风格的模式匹配应用正则性,以及如何利用泛型 Iterable 的一些固有属性、Functional Java 的 Either 类和其他一些元素来模拟强大的 Scala 功能。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程笔记。


推荐阅读
  • Python爬虫中使用正则表达式的方法和注意事项
    本文介绍了在Python爬虫中使用正则表达式的方法和注意事项。首先解释了爬虫的四个主要步骤,并强调了正则表达式在数据处理中的重要性。然后详细介绍了正则表达式的概念和用法,包括检索、替换和过滤文本的功能。同时提到了re模块是Python内置的用于处理正则表达式的模块,并给出了使用正则表达式时需要注意的特殊字符转义和原始字符串的用法。通过本文的学习,读者可以掌握在Python爬虫中使用正则表达式的技巧和方法。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 本文介绍了如何使用python从列表中删除所有的零,并将结果以列表形式输出,同时提供了示例格式。 ... [详细]
  • 浏览器中的异常检测算法及其在深度学习中的应用
    本文介绍了在浏览器中进行异常检测的算法,包括统计学方法和机器学习方法,并探讨了异常检测在深度学习中的应用。异常检测在金融领域的信用卡欺诈、企业安全领域的非法入侵、IT运维中的设备维护时间点预测等方面具有广泛的应用。通过使用TensorFlow.js进行异常检测,可以实现对单变量和多变量异常的检测。统计学方法通过估计数据的分布概率来计算数据点的异常概率,而机器学习方法则通过训练数据来建立异常检测模型。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
author-avatar
大叔丶你好哇塞
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有