💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 1. 前言 为了了解什么是`DSL`,这里我百度了一下: > `DSL`(`domain specific language`),即**领域特定语言**,是编程的一种范式。专门解决某一特定问题的计算机语言,比如大家耳熟能详的 `SQL` 和正则表达式。`DSL` 只专注于某个领域,比如 `SQL` 仅支持数据库的相关处理,而正则表达式只用来检索和替换文本,我们无法用 `SQL` 或者正则表达式来开发一个完整的应用。DSL 往往具备**独特的代码结构和一致的代码风格**。 以上内容摘自:[Kotlin之美——DSL篇 - 简书 (jianshu.com)](https://www.jianshu.com/p/f5f0d38e3e44) # 2. 简单使用DSL ## 2.1 变换 变换是函数式编程的第一大类函数,会遍历集合内容,以值参形式传入的变换器函数,变换每一个元素,然后返回修改后元素。通常使用的函数为`map`和`flatMap`。 ### 2.1.1 map ~~~ fun main() { var users = listOf("张三", "李四", "王五") // 使用map进行变换 val maped = users.map { it -> "我是$it" } println(users) println(maped) } ~~~ 结果: ``` [张三, 李四, 王五] [我是张三, 我是李四, 我是王五] ``` 类似的,这里也可以为`flatMap`来做一个简单的案例。 ### 2.1.2 flatMap 这个函数可以将多个集合合并,返回一个集合。 ~~~ fun main() { var users = listOf( listOf("张三", "张思"), listOf("李四", "李六") ) // 合并集合的集合 val flatMap = users.flatMap { it } println(flatMap) } ~~~ 结果: ``` [张三, 张思, 李四, 李六] ``` ## 2.2 过滤 过滤函数接收一个断言函数,按照条件给出`true`或者`false`,如果按照条件判断为`true`就留在集合,否则被移除。需要注意的是,这里的保留或者移除还是在副本中进行,不修改原本集合。 ### 2.2.1 filter ~~~ fun main() { var users = listOf("张三", "李四", "王五", "赵六") val filteredUsers = users.filter { it.contains("王") } println(users) println(filteredUsers) } ~~~ 结果: ``` [张三, 李四, 王五, 赵六] [王五] ``` 再比如我们需要在一堆数据中找素数: ~~~ fun main() { var datas = listOf( listOf(1, 4, 5, 6, 7, 20), listOf(3, 5, 7, 9, 0, 13) ) // 找datas中的素数 val filter = datas.flatMap { it }.filter { data -> (2 until data).map { data % it } .none { it == 0 } // Returns `true` if no elements match the given [predicate]. } println(filter) } ~~~ 结果: ``` [1, 5, 7, 3, 5, 7, 0, 13] ``` 注意到`(2 until data)`返回的为`[2, ..., data-1]`。由于`filter`的断言函数为`true`或者`false`,这里需要使用`none`来逐个元素判断是否满足断言条件。观看其源码,可以将其写为下面的函数: ~~~ // 等价写法 fun judgement(data: Int): Boolean{ var list: MutableList<Int> = mutableListOf() for (i in 2 until data){ list.add(data % i) } // none for (element in list) { // 如果能够被2到data-1的任意一个数整除,表示不是素数 if (element == 0) return false } return true } val filter1 = datas.flatMap { it }.filter { data -> judgement(data) } println(filter1) ~~~ 结果: ``` [1, 5, 7, 3, 5, 7, 0, 13] ``` 这里再摘抄一下`none`的源代码: ~~~ /** * Returns `true` if no elements match the given [predicate]. * * @sample samples.collections.Collections.Aggregates.noneWithPredicate */ public inline fun <T> Iterable<T>.none(predicate: (T) -> Boolean): Boolean { if (this is Collection && isEmpty()) return true for (element in this) if (predicate(element)) return false return true } ~~~ 当然,有些时候我们需要的是取`topk`个元素,所以这里可以使用`take`函数: ~~~ // 判断一个数是否是素数 【扩展函数】 fun Int.isPrime(): Boolean { (2 until this).map { if(this % it == 0) return false } return true } fun main() { var datas = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 找datas中的素数,并取前3个 val take = datas.filter { data -> data.isPrime() }.take(3) println(take) } ~~~ 结果: ``` [1, 2, 3] ``` ## 2.3 映射合并 ### 2.3.1 zip 也就是使用`zip`函数,进行`key-value`的两个集合的合并。比如下面的案例: ~~~ fun main() { var keys = listOf("zs", "ls", "ww") var users = listOf("张三", "李四", "王五") val zip = keys.zip(users) println(zip) } ~~~ 结果: ``` [(zs, 张三), (ls, 李四), (ww, 王五)] ``` 从上面结果中可以看出,结果为二元组的列表。有时候我们所期望的是一个`Map`集合,所以,我们可以进一步进行处理: ~~~ val zip = keys.zip(users).toMap() ~~~ 结果: ~~~ {zs=张三, ls=李四, ww=王五} ~~~ 不妨来看一下`zip`的源码: ~~~ public infix fun <T, R> Iterable<T>.zip(other: Iterable<R>): List<Pair<T, R>> { return zip(other) { t1, t2 -> t1 to t2 } } ~~~ 可以简单解读下,首先回顾一下`infix`关键字: > 当定义的扩展函数只有一个参数,可以使用`infix`来进行简化,如果定义函数的时候使用了这个关键字,那么点操作符以及参数的括号都可以不要。 - 使用`infix`关键字,可以支持在调用的时候不要点操作符以及参数的括号; - 使用泛型`<T, R>`,方便支持各种类型; - 传入一个列表,返回一个列表对,类型为`<T, R>`,即:调用列表元素类型和传入参数元素类型; ### 2.3.2 fold 接收一个初始累加器的值,然后根据传入的函数进行值的更新,最后进行累加。 ~~~ fun main() { var datas = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) val fold = datas.fold(10) { accumulator, element -> accumulator + element } println(fold) } ~~~ 结果: ``` 65 ``` 其实看`fold`的源码也可以知道其原理: ~~~ public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R { var accumulator = initial for (element in this) accumulator = operation(accumulator, element) return accumulator } ~~~ 其实也就是`for`循环来完成的操作。