从命令式编程到函数式编程:分组问题(以及解决方法)

有相关的分组问题, 例如一整类:

  • 给定一个人的集合,返回一个成对的列表,其中第一个值是年龄,第二个值是那个年龄的人的集合
  • 给定命令的集合,返回对的列表与某些价格范围,例如 $ 0- $ 100 $ 101- $ 200等作为第一值,并且作为第二个这样的订单数
  • 给定一个单词的集合,返回一个成对的列表,其中字母的数量为第一个,而该长度的单词的数量为第二个
  • 等等

这是从命令式函数式编程的重点series.Other职位包括第三职位:

  1. 使用箭头从命令式编程到函数式编程
  2. 从命令式编程到函数式编程,一种方法
  3. 从命令式编程到函数式编程:分组问题(以及解决方法) (本文)
  4. 从命令式编程到函数式编程: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

注意零,这是问题的关键。

更新功能解决方案

命令式解决方案很容易更新:

  1. 查找最长单词的长度
  2. 使用0到此数字的键初始化地图
  3. 通过遍历单词来更新地图值
  4. 再次转换,排序和转换

功能性解决方案要难一些。 如果丢失的键最初不存在,如何添加?

我的第一次尝试被命令性的技巧“污染”了:

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}
}
  1. 糟糕,从不变到可变...
  2. 获取最长单词的长度
  3. 使用可变性!

虽然完全可行,但这种方法在功能领域并不令人满意。

我的第二次尝试利用了数据结构及其组合方式。 想象一下两个列表,上面的一个和第二个特定的列表:

第一清单(索引) 第二个清单(长度)

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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值