Swift90Days - 用函数式编程解决逻辑难题

这篇翻译的文章,用两种方法解决了同一个逻辑难题。第一种方法的编程风格接近大多数 iOS 开发者,实现了指令式编程的解决方案。第二种方法利用了 Swift 的一些语言特性,实现了函数式编程的解决方案。

源代码可以在这里下载:https://github.com/ijoshsmith/break-a-dollar

逻辑难题

前阵子朋友和我说起,把1美元分解成更小的面额,有293种方法。换句话说,如果一个哥们儿告诉你他有1美元,那么他的手里有293种可能的组合,有可能是两个50美分,也可能是4个25美分。第二天,我就开始尝试用代码去解决这个问题。这篇博客回顾了当时想到的两种解决方案。

美元硬币

对于不熟悉美元硬币的同学,可以先了解一下美元的硬币。如下图所示,1美元(dollar) = 100美分(cent):

初探问题

思考后我发现用一种比较简单肮脏的手段解决这个问题并不难,但是这还远远不够。我想找到一种优雅的解决方案,所以我尝试从各个角度思考这个问题,最终得到了想要的答案。

解决这个问题的关键在于递归的分解问题。“如何用各种硬币组合拼成1美元”,更宽泛点讲,其实就是“如何用各种硬币组合拼成指定金额”。

举个人民币的例子。你欠人家100块,人家说你100块都不给我。你说好,我给!于是掏出两张50,这便是一个50+50的解决方案。
这时你发现有一张是崭新的50,你不想给他这张50,于是你的问题变成了:如何用手里的碎钱组合出50面额的钱。
后来你把50换成了5张10块,这便是一个50+10*5的解决方案,然后感觉有一张10块是崭新的,要不我换成硬币给他。
于是问题又变成了:如何组合出10面额的钱。就是这样慢慢拆分下去。

点击 这里 查看完整的算法回顾。

先造硬币

我多次提到“硬币”这个词,实际上一枚硬币也就是一个整数值,代替了它价值多少美分。我写一个枚举类存储所有的硬币面额,然后再用一个静态方法降序返回所有的值:

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,]
    }
}

解决方案1:指令式编程 - Imperative

指令式编程的一个重要观点是:变量改变状态。指令式的程序像是一种微型控制器,它告诉计算机如何完成任务。接下来的 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
}

仔细看下上面的代码,计算过程一共分三步:

  • 首先取出可用数组中的第一个硬币,如果这枚硬币已经是 1 美分,也就是最小的面额,那没有继续拆分的可能性,直接返回1作为结束。
  • 然后创建了一个数组 (smallerCoins) ,存储比当前硬币更小的硬币,用来作为下次调用的参数。
  • 最后计算除去第一次取出的硬币之后,还有多少种解决方案。

这样的代码对于指令式编程来说再平常不过,接下来我们就来看下如何用函数式编程解决这个问题。

解决方案2:函数式编程 - Functional

函数式编程的依赖对象,是函数,而不是状态变化。没有太多的共享数据,就意味着发生错误的可能性更小,需要同步数据的次数也越少。 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 就为空。

我用元组的语法同时获取了 coinsmallerCoins 这两个数据,因为取头取尾可以说是同一个操作。与其写一堆代码去解释如何先取出第一个元素,然后再获取剩下的元素,不如直接用“取出头部和尾部”这样语义化的方式一步到位。

接下来,也并没有采用循环然后改变局部变量的方法来计算剩余的组合数,而是用 reduce 这个高阶函数。如果你对 reduce 这个函数不太熟悉,可以看下这篇文章有个大概的了解。

首先 coin 指当前处理的硬币, coinCounts 是一个数组,里面存储了所有当前面额的硬币的可能出现的数目。比如 amount 是10, coin 是3,那么 coinCounts 的值就是,面额为3的硬币可能有多少。显然应该最多出现3个,所以 coinCounts 是 [1,2,3] 这样的一列数。然后在分别对每种情况进行分解计算。

思考

Swift 对于函数式编程的支持让我感觉的兴奋,Excited!换种方式思考或许是个不小的挑战,但是这都是值得的。几年前我自学了一些 Haskell ,我很欣喜的发现一些函数式思考习惯,让我在 iOS 开发中也能受益匪浅。

示例项目的源代码可以在这里下载。

原文地址:

  • Getting into functional programming with Swift

Swift40/90Days - 用函数式编程解决逻辑难题的更多相关文章

  1. html5使用canvas实现弹幕功能示例

    这篇文章主要介绍了html5使用canvas实现弹幕功能示例的相关资料,需要的朋友可以参考下

  2. 前端实现弹幕效果的方法总结(包含css3和canvas的实现方式)

    这篇文章主要介绍了前端实现弹幕效果的方法总结(包含css3和canvas的实现方式)的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. H5 canvas实现贪吃蛇小游戏

    本篇文章主要介绍了H5 canvas实现贪吃蛇小游戏,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  4. 吃透移动端 Html5 响应式布局

    这篇文章主要介绍了吃透移动端 Html5 响应式布局,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  5. ios – parse.com用于键,预期字符串的无效类型,但是得到了数组

    我尝试将我的数据保存到parse.com.我已经预先在parse.com上创建了一个名为’SomeClass’的类.它有一个名为’mySpecialColumn’的列,其数据类型为String.这是我尝试使用以下代码保存数据的代码:如果我运行这个我得到:错误:密钥mySpecialColumn的无效类型,预期字符串,但得到数组这就是我在parse.com上的核心外观:有谁知道我为什么会收到这个错误?

  6. ios – 上下文类型’NSFastEnumeration’不能与数组文字一起使用

    斯威夫特3,你会这样做吗?解决方法正如您所发现的,您不能使用as-casting将数组文字的类型指定为NSFastEnumeration.您需要找到一个符合NSFastEnumeration的正确类,在您的情况下它是NSArray.通常写这样的东西:

  7. 如何在iOS上快速将ALAsset映像保存到磁盘?

    我正在使用ALAsset来检索这样的图像:这返回CGImageRef,我想尽快保存到磁盘…解决方案1:解决方案2:问题是两种方法在设备上的执行速度都很慢.每张图片大约需要2秒才能执行此操作.这绝对是长久的.问题:如何加快图像保存过程?或许还有更好的解决方案吗?

  8. ios – 获取资产目录文件夹中所有图像的数组

    在iOS中,是否可以获取资产目录文件夹中的图像数组?我不确定为什么会对此进行投票.我真的不知道从哪里开始.我的另一种方法是创建文件夹中所有文件的plist,但它似乎是多余的.我无法添加任何代码,因为我会添加什么?

  9. ios – 来自调试器的消息:由于内存问题而终止

    我的应用程序使用Geojson文件.我使用MapBoxSDK将MGLpolyline添加到地图中.但问题是我的文件太大,以至于应用程序崩溃并收到错误:来自调试器的消息:由于内存问题而终止.我在第一次循环时面对66234个对象.我试图将数组块化为新数组,但没有成功.请帮我解决问题.这是我在地图上绘制的代码,这里是我的testprojectongithubuseXcode8.1如果有任何不同的第三方可

  10. ios – Swift – 使用字典数组从字典访问数据时出错

    我有一个非常简单的例子,说明我想做什么基本上,我有一个字典,其值包含[String:String]字典数组.我把数据填入其中,但当我去访问数据时,我收到此错误:Cannotsubscriptavalueoftype‘[([String:String])]?’withanindexoftype‘Int’请让我知道我做错了什么.解决方法您的常量数组是可选的.订阅字典总是返回一个可选项.你必须打开它.更

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部