树和模式匹配

本文探讨了函数式编程的概念,特别是在Java中使用Either抽象进行树形结构的构建和遍历。通过模仿Scala的模式匹配,展示了如何在Java中实现深度查找、元素存在性和出现次数的计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一部分中 ,我介绍了函数式编程世界中的一个通用抽象: Either 。 在本期中,我将继续探索Either ,使用它来构建树形结构-从而使我能够模仿Scala的模式匹配来构建遍历方法。

在Java中使用泛型,一个Either成为一个单一的数据结构,它可以接受的两种类型,其中存储在leftright的部分。

在上一部分的罗马数字解析器示例中, Either拥有Exception (左)或Integer (右),如图1所示:

图1.两种类型的Either抽象
两种抽象都可以包含两种类型

在该示例中,此分配填充Either

Either<Exception, Integer> 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中 ,函数的整个主体包括应用于valuematch 。 对于每个选项, 模式保护使我能够根据参数以及参数的类型来过滤匹配项。 此语法的优点是选项的干净分区,而不是繁琐的if语句系列。

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

清单2. Scala中的匹配案例类
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来对所有可能的选项进行模式匹配,包括最后一种情况,后者处理null

Java没有模式匹配,因此它无法复制Scala创建清晰可读的调度代码的功能。 但是将泛型和众所周知的数据结构结合起来可以使它接近,这让我回到了Either

两棵树

可以使用三个抽象为树数据结构建模,如表1所示:

表1.用三个抽象构建一棵树
空的 单元格没有价值
单元格具有特定数据类型的值
节点 指向其他Leaf或Node

在上一期文章中 ,我实现了Either类的简单版本,但是为了方便起见,在本示例中,我将使用Functional Java框架中的一个版本(请参阅参考资料 )。 从概念上讲, Either抽象可扩展到所需的任意多个插槽。 例如,考虑声明Either<Empty, Either<Leaf, Node>> ,它创建了一个三部分的数据结构,如图2所示:

图2. Either<Empty, Either<Leaf, Node>>的数据结构
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<Empty, Either<Leaf, Node>> toEither();

    public static final class Empty extends Tree {
        public Either<Empty, Either<Leaf, Node>> toEither() {
            return left(this);
        }

        public Empty() {}
    }

    public static final class Leaf extends Tree {
        public final int n;

        public Either<Empty, Either<Leaf, Node>> toEither() {
            return right(Either.<Leaf, Node>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<Empty, Either<Leaf, Node>> toEither() {
            return right(Either.<Leaf, Node>right(this));
        }

        public Node(Tree left, Tree right) {
            this.left = left;
            this.right = right;
        }
    }
}

清单3中的抽象Tree类在其中定义了三个final具体类: EmptyLeafNode 。 在内部, Tree类使用如图2所示的三槽式Either ,这约定如下:最左侧的插槽始终保持Empty ,中间的插槽保持Leaf ,最右侧的插槽保持Node 。 为此,它要求每个类实现toEither()方法,并为该类型返回适当的“槽”。 在数据结构中的每个“细胞”,是在传统的计算机科学意义上的工会 ,设计成只能容纳三种可能的类型之一,在任何给定的时间。

有了这种树结构,并且我知道它的内部结构是基于<Either, <Left, Node>>的事实,我可以模拟模式匹配以访问树中的每个元素。

模式匹配以进行树遍历

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<Leaf, Node> 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()方法是一个递归的深度查找函数。 因为我的树是基于特定的数据结构( <Either, <Left, Node>> ),所以我可以将每个“插槽”视为特定情况。 如果单元格为空,则此分支没有深度。 如果单元格是一片叶子,我将其视为树级。 如果单元格是一个节点,我知道我应该递归搜索左侧和右侧,为另一个递归级别添加1

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

清单5.确定树中的存在
static public boolean inTree(Tree t, int value) {
    for (Empty e : t.toEither().left())
        return false;
    for (Either<Leaf, Node> 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;
}

和以前一样,我为数据结构中每个可能的“槽”指定返回值。 如果遇到一个空单元格,则返回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<Leaf, Node> 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功能。

在下一部分中,我将开始研究下一代Java语言中存在的各种方法调度机制,这与Java中可用的有限选项形成对比。


翻译自: https://www.ibm.com/developerworks/java/library/j-ft14/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值