Kata –在Kotlin中实现功能性List数据结构

我在出色的Scala函数式编程》一书的第3章中看到了一个练习,该练习处理了定义函数数据结构的问题,并以链表为例说明了如何开发这样的数据结构。 我想使用Kotlin尝试该示例,以了解我可以在多大程度上复制该示例。

样品的Scala的骨架可在同伴码书在这里和我在Kotlin尝试通过信息库中的大量answerkey启发(复制!)。

基本的

这是Kotlin中基本的List表示形式:

sealed class List<out A> {

    abstract val head: A

    abstract val tail: List<A>
}

data class Cons<out T>(override val head: T, override val tail: List<T>) : List<T>()

object Nil : List<Nothing>() {
    override val head: Nothing
        get() {
            throw NoSuchElementException("head of an empty list")
        }

    override val tail: List<Nothing>
        get() {
            throw NoSuchElementException("tail of an empty list")
        }
}

List已定义为密封类,这意味着密封类的所有子类都将在同一文件中定义。 这对于实例类型的模式匹配很有用,并且在大多数功能中都会反复出现。

此列表有两种实现方式–

1.包含一个由头部元素和尾部列表组成的非空列表,

2.空列表

就目前的形式而言,这已经非常有用,请考虑以下内容以构造List并从中检索元素:

val l1:List<Int> = Cons(1, Cons(2, Cons(3, Cons(4, Nil))))
assertThat(l1.head).isEqualTo(1)
assertThat(l1.tail).isEqualTo(Cons(2, Cons(3, Cons(4, Nil))))


val l2:List<String> = Nil

模式匹配“何时”表达

现在跳到实现List的一些方法。 由于List是一个密封的类,因此它允许一些良好的模式匹配,例如,获取List中元素的总和:

fun sum(l: List<Int>): Int {
    return when(l) {
        is Cons -> l.head + sum(l.tail)
        is Nil -> 0
    }
}

编译器知道Cons和Nil是列表实例上进行匹配的仅有两条路径。

稍微复杂一点的操作是,从列表的开头“删除”一些元素,然后“ dropWhile”接收一个谓词,然后从与该谓词匹配的开头删除元素:

fun drop(n: Int): List<A> {
    return if (n <= 0)
        this
    else when (this) {
        is Cons -> tail.drop(n - 1)
        is Nil -> Nil
    }
}

val l = list(4, 3, 2, 1)
assertThat(l.drop(2)).isEqualTo(list(2, 1))

fun dropWhile(p: (A) -> Boolean): List<A> {
    return when(this) {
        is Cons -> if (p(this.head)) this.tail.dropWhile(p) else this
        is Nil -> Nil
    }
}

val l = list(1, 2, 3, 5, 8, 13, 21, 34, 55, 89)
assertThat(l.dropWhile({e -> e < 20})).isEqualTo(list(21, 34, 55, 89))

这些展示了模式匹配和Kotlin中“ when”表达的强大功能。

不安全的差异!

要抚平皱纹,请查看如何使用声明为“ out T”的类型参数定义List,这称为“声明位点差异”,在这种情况下,它使List在类型T上协变。 Kotlin文档对它进行了精美的解释。 通过声明List的方式,它使我可以执行以下操作:

val l:List<Int> = Cons(1, Cons(2, Nil))
val lAny: List<Any> = l

现在,考虑一个“附加”功能,该功能附加另一个列表:

fun append(l: List<@UnsafeVariance A>): List<A> {
    return when (this) {
        is Cons -> Cons(head, tail.append(l))
        is Nil -> l
    }
}

此处将第二个列表作为附加函数的参数,但是Kotlin会标记该参数–这是因为可以返回协变量类型,但不能将其作为参数。 但是,由于我们知道List的当前形式是不可变的,因此可以通过在类型参数上标记“ @UnsafeVariance”注释来克服这一问题。

折叠式

折叠操作允许基于列表中各个元素的某种聚合将列表“折叠”为结果。

考虑foldLeft:

fun <B> foldLeft(z: B, f: (B, A) -> B): B {
    tailrec fun foldLeft(l: List<A>, z: B, f: (B, A) -> B): B {
        return when (l) {
            is Nil -> z
            is Cons -> foldLeft(l.tail, f(z, l.head), f)
        }
    }

    return foldLeft(this, z, f)
}

如果列表由元素(2、3、5、8)组成,则foldLeft等效于“ f(f(f(f(f(z,2),3),5),8)”

有了这个高阶函数,求和函数可以这样表示:

val l = Cons(1, Cons(2, Cons(3, Cons(4, Nil))))
assertThat(l.foldLeft(0, {r, e -> r + e})).isEqualTo(10)

foldRight在Kotlin中如下所示:

fun <B> foldRight(z: B, f: (A, B) -> B): B {
    return when(this) {
        is Cons -> f(this.head, tail.foldRight(z, f))
        is Nil -> z
    }
}

如果列表由元素(2、3、5、8)组成,则foldRight等效于“ f(2,f(3,f(5,f(8,z))))”

这个版本的foldRight尽管看上去不是很酷的尾部递归,但是可以通过使用先前定义的尾部递归foldLeft来实现更友好的堆栈版本,方法是简单地反转List并以下列方式在内部调用foldLeft:

fun reverse(): List<A> {
    return foldLeft(Nil as List<A>, { b, a -> Cons(a, b) })
}

fun <B> foldRightViaFoldLeft(z: B, f: (A, B) -> B): B {
    return reverse().foldLeft(z, { b, a -> f(a, b) })
}

地图和flatMap

map是转换此列表元素的函数:

fun <B> map(f: (A) -> B): List<B> {
    return when (this) {
        is Cons -> Cons(f(head), tail.map(f))
        is Nil -> Nil
    }
}

以下是使用此功能的一个示例:

val l = Cons(1, Cons(2, Cons(3, Nil)))
val l2 = l.map { e -> e.toString() }
assertThat(l2).isEqualTo(Cons("1", Cons("2", Cons("3", Nil))))

map的一种变体,其中转换函数返回另一个列表,最终结果使所有内容变平,最好在实现后使用示例进行演示:

fun <B> flatMap(f: (a: A) -> List<@UnsafeVariance B>): List<B> {
    return flatten(map { a -> f(a) })
}

companion object {
    fun <A> flatten(l: List<List<A>>): List<A> {
        return l.foldRight(Nil as List<A>, { a, b -> a.append(b) })
    }
}


val l = Cons(1, Cons(2, Cons(3, Nil)))

val l2 = l.flatMap { e -> list(e.toString(), e.toString()) }

assertThat(l2)
        .isEqualTo(
                Cons("1", Cons("1", Cons("2", Cons("2", Cons("3", Cons("3", Nil)))))))

这涵盖了使用Kotlin实现功能列表数据结构所涉及的基础知识,与scala版本相比,存在一些粗糙之处,但我认为它大部分都可以工作。 诚然,可以对示例进行大幅改进,如果您对如何改进代码有任何意见,请给我发送PR,
github回购此样本或作为对此文章的评论。

翻译自: https://www.javacodegeeks.com/2017/10/kata-implementing-functional-list-data-structure-kotlin.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值