命令式编程,函数响应式编程
有相关的分组问题, 例如一整类:
- 给定一个人的集合,返回一个成对的列表,其中第一个值是年龄,第二个值是那个年龄的人的集合
- 给定一个订单集合,返回一个具有某个价格范围( 例如 $ 0- $ 100,$ 101- $ 200等)的对的列表作为第一个值,而此类订单的数量作为第二个
- 给定单词的集合,返回一个对的列表,其中字母的数量为首,该长度的单词的数量为第二
- 等等
这是从命令式函数式编程的重点series.Other职位包括第三职位:
- 使用箭头从命令式编程到函数式编程
- 从命令式编程到函数式编程,一种方法
- 从命令式编程到函数式编程:分组问题(以及解决方法) (本文)
- 从命令式编程到函数式编程:Dijkstra算法
当务之急
这是一个解决最终要求的函数,它以命令式方式编写:
funimperative(strings:Array<String>):List<Pair<Int,Int>>{
valmap=mutableMapOf<Int,Int>()
strings.forEach{
vallength=it.length
if(map.containsKey(length))map[length]=map[length]asInt+1
elsemap[length]=1
}
valpairs=mutableListOf<Pair<Int,Int>>()
map.forEach{
pairs.add(it.keytoit.value)
}
pairs.sortBy{it.first}
returnpairs
}
我相信这是足够可读的,因此我不需要解释。 也可以用自己喜欢的语言提出类似的解决方案。
例如,以下输入arrayOf("a", "an", "the", "ace", "little", "six", "seven", "ten", "eleven")
产生:
(1, 1) // "a" (2, 1) // "an" (3, 4) // "the" "ace" "six" "ten" (5, 1) // "seven" (6, 2) // "little" "eleven"
功能替代
等效的功能代码更简洁,更重要的是更易于阅读:
funfunctional(strings:Array<String>):List<Pair<Int,Int>>=strings.groupBy{it.length}
.map{it.keytoit.value.count()}
.sortedBy{it.first}
}
改变需求
从我的立场来看,功能方法似乎更合适。 但是,让我们以一些其他限制/要求的形式来敲一下齿轮:
- 每个键都必须列出, 即使没有关联值的键也必须列出
- 密钥本身绝不能返回,只能返回值
- 顺序必须与前一种情况相同(按字长)
以上测试样本应返回:
1 1 4 0 1 2
注意零,这是问题的关键。
更新功能解决方案
强制性解决方案很容易更新:
- 查找最长单词的长度
- 使用0到此数字的键初始化地图
- 通过遍历单词来更新地图值
- 再次转换,排序和转换
功能性解决方案要难一些。 如果丢失的键最初不存在,如何添加?
我的第一次尝试被命令性的技巧“污染”了:
funfunctional2(strings:Array<String>):List<Int>{
returnstrings.groupBy{it.length}
.toMutableMap() (1)
.apply{
valmax=strings.map{it.length}.max()?:0 (2)
(1..max).forEach{
computeIfAbsent(it){arrayListOf()} (3)
}
}
.map{it.keytoit.value.count()}
.sortedBy{it.first}
.map{it.second}
}
- 糟糕,从不变到可变...
- 获取最长单词的长度
- 使用可变性!
尽管完全可行,但这种方法在功能领域并不令人满意。
我的第二次尝试利用了数据结构及其组合方式。 想象两个清单,上面一个清单和第二个特定清单:
第一清单(索引) | 第二个清单(长度) | ||
---|---|---|---|
First value | Second value | First value | Second value |
Word length | 0 | Word length | Number of words |
可以合并这些列表,然后根据第一个值(字长)使元素区分。 因此,如果第二个列表中存在一对,它将覆盖第一个列表中的值-0。否则,将保留值为0的初始对。
funfunctional3(strings:Array<String>):List<Int>{
valmax=strings.map{it.length}.max()?:0
valindices:List<Pair<Int,Int>>=(1..max).map{itto0}
vallengths:List<Pair<Int,Int>>=strings.groupBy{it.length}
.map{it.keytoit.value.count()}
returnlengths.union(indices)
.distinctBy{it.first}
.sortedBy{it.first}
.map{it.second}
}
奖励:一些Clojure的爱
至于上周,这里有一些Clojure代码可以实现相同的功能:
(use '[clojure.algo.generic.functor :only (fmap)])
(defn functional [strings]
(let [max (apply max (map count strings))
lengths (fmap count (group-by count strings))
indices (zipmap (range 1 max)
(repeat max 0))]
(vals (merge indices lengths)))
)
在此代码段中,映射优先于成对列表,以利用merge
功能。 Kotlin中没有这样的地图合并功能(至少我不了解)。
结论
与命令式编程相比,函数式编程使代码更简洁。 但是,提高可读性还需要了解API和库中的大多数可用功能。
翻译自: https://blog.frankel.ch/imperative-functional-programming/3/
命令式编程,函数响应式编程