java函数为基础

java函数为基础在阅读这篇文章前 请务必阅读这三篇文章 定义列表 在函数式编程里 列表是递归定义的 即 列表的第一个元素 称为 head 列表的剩余部分还是一个列表 称为 tail 正如你处理递归调用需要一个终止条件 列表也是如此 我们称这个终止情况为 Nil 或者叫它空列表 它没有 head 和 tail 因此 我们需要两种类来表示链表 一个表示非空链表 我们使用 Cons 来表示 借鉴了

大家好,我是讯享网,很高兴认识大家。



 在阅读这篇文章前,请务必阅读这三篇文章

定义列表

在函数式编程里,列表是递归定义的,即:

    列表的第一个元素,称为head;

    列表的剩余部分还是一个列表,称为tail;

正如你处理递归调用需要一个终止条件,列表也是如此,我们称这个终止情况为Nil,或者叫它空列表,它没有head和tail。因此,我们需要两种类来表示链表,一个表示非空链表(我们使用Cons来表示。借鉴了 Lisp家族中的cons操作),另一个表示空链表(Nil)。

例如,这有个链表,存储abcdef

 

基本实现如下:

public abstract class List<T> {

       public abstract T head();

       public abstract List<T> tail();

       public abstract boolean isEmpty();

       private List() {

       }

java函数为基础

       private static class Nil<T> extends List<T> {

              private Nil() {

              }

              @Override

              public T head() {

                     throw new IllegalStateException("this is empty");

              }

              @Override

              public List<T> tail() {

                     throw new IllegalStateException("this is empty");

              }

              @Override

              public boolean isEmpty() {

                     return true;

              }

       }

       private static class Cons<T> extends List<T> {

              private final T head;

              private final List<T> tail;

              private Cons(T head, List<T> tail) {

                     this.head = head;

                     this.tail = tail;

              }

              @Override

              public T head() {

                     return head;

              }

              @Override

              public List<T> tail() {

                     return tail;

              }

              @Override

              public boolean isEmpty() {

                     return false;

              }

       }

}

    类型参数T表示列表持有的元素类型,List类里有三个主要的抽象方法,head()用来返回列表的第一个元素,tail()返回列表的剩余元素,isEmpty()来判断列表是否为空。将类设为私有,这样你必须通过静态工厂方法才可以创建列表。

       @SuppressWarnings("rawtypes")

       private static final List NIL = new Nil<>();

       @SuppressWarnings("unchecked")

       public static <T> List<T> list() {

              return NIL;

       }

       @SafeVarargs

       public static <T> List<T> list(T... t) {

              List<T> newList = list();

              for (int i = t.length - 1; i >= 0; i--) {

                     newList = new Cons<T>(t[i], newList);

              }

              return newList;

       }

    我们在这里向list类添加了一个新属性,它没有类型参数,是一个原生类型。换句话说,它可以表示元素类型为任意的空列表,这样我们就不需要为每一个参数类型创建一个不同的空列表了。上面两个静态方法是通用的,所以放在List类里。

操作列表:

1:向大家展示列表—toString方法:

String返回一个列表的字符串描述,

这个方法每个类要分别实现:

在Nil类里实现非常简单:

            @Override

              public String toString() {

                     return "[NIL]";

              }

    而在Cons类里,我们使用StringBuilder作为一个累加器。

    我们先看第一个版本的实现:

            @Override

              public String toString() {

                     return toString(new StringBuilder(), this);

              }

              private String toString(StringBuilder acc,List<T> list) {

                     return list.isEmpty()

                                   ?acc.append("Nil").toString()

                                   :toString(acc.append(list.head()).append(", "), list.tail());

              }

    这是一个递归的方法,操作也符合列表的定义,但是当列表很大时,可能会发生爆栈的现象,我们现在按照前面讲的把它改成栈安全的:

            @Override

              public String toString() {

                     return String.format("[%sNil]", toString(new StringBuilder(), this) .eval());

              }

              private TailCall<StringBuilder> toString(StringBuilder acc,List<T> list) {

                     return list.isEmpty()

                                   ?ret(acc)

                                                 :sus(()->toString(acc.append(list.head()).append(", "),list.tail()));

              }

2在头部添加元素—cons方法。

    要知道,一个Nil添加一个元素后就是Cons了,所以这个方法在两个子类中实现相同,可以放在List类里面:

public Cons<T> cons(T head){

              return new Cons<>(head, this);

       }

3.替换操作—设置头部新制—setHead方法

    public abstract List<T> setHead(T newHead);

    Nil类实现很简单

            @Override

              public List<T> setHead(T newHead) {

                     throw new IllegalStateException("this is empty");

              }

Cons类实现如下:

       @Override

              public List<T> setHead(T newHead) {

                     return new Cons<T>(newHead, tail());

              }

4.删除前n个元素—drop方法

列表里tail方法产生的效果与删除第一个元素的效果一样,我们可以使用它来帮我们完成drop方法。

public abstract List<T> drop(int n);

Nil类实现很简单

       @Override

              public List<T> drop(int n) {

                     return this;

              }

Cons类实现如下:

           @Override

              public List<T> drop(int n) {

                     return n <= 0 ? this : drop0(this, n).eval();

              }

              private TailCall<List<T>> drop0(List<T> list, int n) {

                     return n <= 0 || list.isEmpty() ? ret(this) : sus(() -> drop0(list.tail(), n - 1));

              }

这个方法有一个变种,dropWhile(Function<T,Boolean> p),只要条件为真,就不断的删除,只要遇到一个不满足条件的元素就终止。在Cons类里,它是这样实现的:

              @Override

              public List<T> dropWhile(Function<T, Boolean> p) {

                     return dropWhile0(this, p).eval();

              }

              private TailCall<List<T>> dropWhile0(List<T> list, Function<T, Boolean> p) {

                     return list.isEmpty() || p.apply(list.head()).equals(Boolean.FALSE) ? ret(list)

                                   : sus(() -> dropWhile0(list.tail(), p));

              }

5.反转列表—reverse方法

    我们的链表只能从头部开始操作,当遇到删除最后一个非空元素,或者做链表的链接时,这很不方便,因此我们需要一个能将链表反转的方法。(NIL反转后还是NIL,这里不做介绍,它返回this)

    要知道,我们创建新链表(cons操作),它只能向前添加,我们可以利用它来反转链表。

    原理如下:

    这是一个带反转的链表

原列表

    反转操作前我们都需要一个Nil,实际上它是同一个,由list()方法返回。

 

反转过程

            @Override

              public List<T> reverse() {

                     return reverse0(list(), this).eval();

              }

              private TailCall<List<T>> reverse0(List<T> acc, List<T> list) {

                     return list.isEmpty() ? ret(acc) : sus(() -> reverse0(new Cons<>(list.head(), acc), list.tail()));

              }

6.删除末尾元素—dropLast

    Nil类抛出异常,我们主要看Cons类如何操作(使用上一个反转方法实现):

            @Override

              public List<T> dropLast() {

                     return reverse().tail().reverse();

              }

 

7.连接操作-concat方法

    如图,这有两个要进行连接的链表,你要注意连接后还要保持原有的元素顺序。

    结果可能是这样:

 

    也可能是这样。

 

    我们只能操作头元素,所以你可以将某个链表反转后执行连接。这个方法对两个子类都使用,所以放在List类里。

public static <T> List<T> concatByreverse(List<T> list1, List<T> list2) {

              List<T> newList1 = list1.reverse();

              while (!newList1.isEmpty()) {

                     list2 = new Cons<T>(newList1.head(), list2);

              }

              return list2;

       }

    又或者我们不做反转:

public static <T> List<T> concat(List<T> list1, List<T> list2) {

              return list1.isEmpty() ? list2 : new Cons<T>(list1.head(), concat(list1.tail(), list2));

       }

        如果List1太长,可能会导致爆栈,你也可以尝试使用我们前面讲过的TailCall优化。

这里连接操作的时间复杂度只与第一个链表有关,所以实际操作中要考虑好将谁作为第一个链表。你可以看出我并没有给出栈安全的方法实现,因为在下一章中,你将看到更简单的实现方式。

8.遍历操作—foreach方法

    这是一个对两个类都适用的方法,故放在List类里

    public void forEach(Consumer<T> f) {

              List<T> workList = this;

              while (!workList.isEmpty()) {

                     f.accept(workList.head());

                     workList = workList.tail();

              }

       }

9.转换为Java里的列表形式—toJavaList方法

    这个操作很简单

       public java.util.List<T> toJavaList() {

              java.util.List<T> newList = new ArrayList<>();

              List<T> workList = this;

              while (!workList.isEmpty()) {

                     newList.add(workList.head());

                     workList = workList.tail();

              }

              return newList;

       }

         下一章我将介绍如何使用高阶函数操作列表。

 

 

小讯
上一篇 2024-12-31 17:23
下一篇 2024-12-24 13:08

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/10154.html