Swift90Days - 用函数式编程解决逻辑难题git
这篇翻译的文章,用两种方法解决了同一个逻辑难题。第一种方法的编程风格接近大多数 iOS 开发者,实现了指令式编程的解决方案。第二种方法利用了 Swift 的一些语言特性,实现了函数式编程的解决方案。github
源代码能够在这里下载:https://github.com/ijoshsmith/break-a-dollar算法
前阵子朋友和我提及,把1美圆分解成更小的面额,有293种方法。换句话说,若是一个哥们儿告诉你他有1美圆,那么他的手里有293种可能的组合,有多是两个50美分,也多是4个25美分。次日,我就开始尝试用代码去解决这个问题。这篇博客回顾了当时想到的两种解决方案。编程
对于不熟悉美圆硬币的同窗,能够先了解一下美圆的硬币。以下图所示,1美圆(dollar) = 100美分(cent):swift
思考后我发现用一种比较简单肮脏的手段解决这个问题并不难,可是这还远远不够。我想找到一种优雅的解决方案,因此我尝试从各个角度思考这个问题,最终获得了想要的答案。数组
解决这个问题的关键在于递归的分解问题。“如何用各类硬币组合拼成1美圆”,更宽泛点讲,其实就是“如何用各类硬币组合拼成指定金额”。app
举我的民币的例子。你欠人家100块,人家说你100块都不给我。你说好,我给!因而掏出两张50,这即是一个50+50的解决方案。
这时你发现有一张是崭新的50,你不想给他这张50,因而你的问题变成了:如何用手里的碎钱组合出50面额的钱。
后来你把50换成了5张10块,这即是一个50+10*5的解决方案,而后感受有一张10块是崭新的,要不我换成硬币给他。
因而问题又变成了:如何组合出10面额的钱。就是这样慢慢拆分下去。编程语言
点击 这里 查看完整的算法回顾。wordpress
我屡次提到“硬币”这个词,实际上一枚硬币也就是一个整数值,代替了它价值多少美分。我写一个枚举类存储全部的硬币面额,而后再用一个静态方法降序返回全部的值:函数式编程
enum Coin: Int { case SilverDollar = 100 case HalfDollar = 50 case Quarter = 25 case Dime = 10 case Nickel = 5 case Penny = 1 static func coinsInDescendingOrder() -> [Coin] { return [ Coin.SilverDollar, Coin.HalfDollar, Coin.Quarter, Coin.Dime, Coin.Nickel, Coin.Penny, ] } }
指令式编程的一个重要观点是:变量改变状态。指令式的程序像是一种微型控制器,它告诉计算机如何完成任务。接下来的 Swift 代码你们看起来应该都不陌生,由于 objc 就是一种指令式的编程语言:
func countWaysToBreakAmout(amount: Int, usingCoins coins:[Coin]) -> Int{ let coin = coins[0] if (coin == .Penny) { return 1 } var smallerCoins = [Coin]() for index in 1..<coins.count { smallerCoins.append(coins[index]) } var sum = 0 for coinCount in 0...(amount/coin.rawValue) { let remainingAmount = amount - (coin.rawValue * coinCount) sum += countWaysToBreakAmout(remainingAmount, usingCoins: smallerCoins) } return sum }
仔细看下上面的代码,计算过程一共分三步:
smallerCoins
) ,存储比当前硬币更小的硬币,用来做为下次调用的参数。这样的代码对于指令式编程来讲再日常不过,接下来咱们就来看下如何用函数式编程解决这个问题。
函数式编程的依赖对象,是函数,而不是状态变化。没有太多的共享数据,就意味着发生错误的可能性更小,须要同步数据的次数也越少。 Swift 中函数已是一等公民,这让高阶函数变成可能,也就是说,一个函数能够是经过其它函数组装构成的。随着 objc 中 block 的引入, iOS 开发者对这个应该并不陌生。
下面是个人函数式解决方案:
func countWaysToBreakAmount(amount: Int, usingCoins coins:Slice<Coin>) -> Int{ let (coin, smallerCoins) = (coins[0], coins[1..<coins.count]) if (coin == .Penny) { return 1 } let coinCounts = [Int](0...amount/coin.rawValue) return coinCounts.reduce(0) { (sum, coinCount) in let remainingAmount = amount - (coin.rawValue * coinCount) return sum + self.countWaysToBreakAmount(remainingAmount, usingCoins: smallerCoins) } }
第二个参数是 Slice<Coin>
而不是数组,由于不必把硬币拷贝到新的数组里。咱们只须要用数组的一个切片就能够,也就是第一行代码里的 smallerCoins
,在函数式编程里称之为 tail
。咱们把数据中的第一个元素称之为 head
,剩下来的部分称之为 tail
。将数组进行切分在下标越界的状况下也不会引起异常。若是数组中只剩下一个元素,这时 smallerCoins
就为空。
我用元组的语法同时获取了 coin
和 smallerCoins
这两个数据,由于取头取尾能够说是同一个操做。与其写一堆代码去解释如何先取出第一个元素,而后再获取剩下的元素,不如直接用“取出头部和尾部”这样语义化的方式一步到位。
接下来,也并无采用循环而后改变局部变量的方法来计算剩余的组合数,而是用 reduce
这个高阶函数。若是你对 reduce
这个函数不太熟悉,能够看下这篇文章有个大概的了解。
首先 coin
指当前处理的硬币, coinCounts
是一个数组,里面存储了全部当前面额的硬币的可能出现的数目。好比 amount
是10, coin
是3,那么 coinCounts
的值就是,面额为3的硬币可能有多少。显然应该最多出现3个,因此 coinCounts
是 [1,2,3] 这样的一列数。而后在分别对每种状况进行分解计算。
Swift 对于函数式编程的支持让我感受的兴奋,Excited!换种方式思考或许是个不小的挑战,可是这都是值得的。几年前我自学了一些 Haskell ,我很欣喜的发现一些函数式思考习惯,让我在 iOS 开发中也能受益不浅。
示例项目的源代码能够在这里下载。
原文地址: