编写单链表反转的程序

首先,我们定义一个链表。

struct list {
    int value;
    struct list * next;
};

C 语言也要面向对象编程,于是,编写构造函数。

struct list * makeList(int value, struct list * next){
    struct list * this = (struct list*)malloc(sizeof(struct list));
    this->value = value;
    this->next = next;
}

编写反转程序。

struct list * reverse(struct list * L) {
    struct list * result = NULL;
    for(struct list * i = L; i != NULL; i = i->next){
        result = makeList(i->value, result);
    }
    return result;
}

OK,让我们测试一下。

void printList(struct list * L)
{
    printf("(");
    int n = 0;
    for(struct list * i = L; i != NULL; i = i->next, ++n){
        if(n != 0){  printf(","); };
        printf("%d", i->value);
    }
    printf(")");
    return;
}
int main(int argc, char *argv[])
{
    struct list * a = makeList(1, NULL);
    a = makeList(2, a);
    a = makeList(3, a);
    printf("before reverse:");
    printList(a);
    printf("\nafter reverse:");
    printList(reverse(a));
    printf("\n");

    printf("before reverse:");
    printList(NULL);
    printf("\nafter reverse:");
    printList(reverse(NULL));
    printf("\n");
    return 0;
}

程序输出

before reverse:(3,2,1)
after reverse:(1,2,3)
before reverse:()
after reverse:()

这个程序的风格是 imperative 的,如果使用函数式编程,我们得换一个语言 Java 。

    public static class List<T> {
        private final T value;
        private final List<T> next;

        public List(T value, List next) {
            this.value = value;
            this.next = next;
        }

        @Override
        public String toString() {
            int n = 0;
            StringBuilder sb = new StringBuilder();
            sb.append("(");
            for(List i = this; i!=null; i = i.next, n ++){
                if(n!=0) {
                    sb.append(",");
                }
                sb.append(i.value);
            }
            sb.append(")");
            return sb.toString();
        }
    }

构造函数

    public static <T> List<T> cons(T value, List<T> next) {
        return new List(value, next);
    }

著名的 foldl 函数,一切一次遍历循环的抽象。

    public static <T,R> R foldl(BiFunction<T,R,R> f, R r, List<T> l){
        if(l == null){
            return r;
        }else{
            return foldl(f, f.apply(l.value, r), (List<T>)l.next);
        }
    }

可惜 java 不支持 TCO (Tail Call Optimization) ,这种写法会爆栈的。于是,用命令式的编程风格再写一次。

    public static <T,R> R foldl(BiFunction<T,R,R> f, R init, List<T> l){
        R result = init;
        for(List<T> i = l; i!=null; i = i.next){
            result = f.apply(i.value, result);
        }
        return result;
    }

反转列表的函数

    static <T> List<T> reverse(List<T> l) {
        return foldl(ListTest::cons, null, l);
    }

测试代码

    @Test
    public void main() throws Exception {
        List a = new List(1, null);
        a = new List(2, a);
        a = new List(3, a);

        System.out.println("before reverse: " + a);

        List b = reverse(a);
        System.out.println("after reverse: " + b);
    }

输出结果

before reverse: (3,2,1)
after reverse: (1,2,3)

foldl 可以抽象很多一次遍历的循环,例如,max 可以如下

    static <T extends Comparable<? super T>> T max(List<T> l){
        if(l == null) return null;
        return foldl((r1,r2) -> Comparator.<T>naturalOrder().compare(r1,r2) > 0?r1:r2,l.value, l);
    }

在函数式编程中,reverse-foldl 是一个俗语。例如 map 可以写成下面的形式。

    static <T,R> List<R> map(Function<T,R> f, List<T> l) {
        return reverse(foldl((r,n) -> cons(f.apply(r),n), null,l));
    }

测试代码

    @Test
    public void main2() throws Exception {
        List<Integer> a = new List(1, null);
        a = new List(2, a);
        a = new List(3, a);
        System.out.println("map: " + map(v-> v + 100, a));
    }

输出结果

map: (103,102,101)