计算特定字符在字符串中出现的次数

问题:随便给定一个字符串儿,如何计算一个特定字符在该串儿中出现的次数?

  比如说,我现在给定这样一段字符串儿:“The US State Department on Friday has officially delivered notice to the UN of its intentions to withdraw from the 2015 Paris climate pact.”,你能告诉我它里面有多少个字符i吗?

计算给定字符串儿中特定字符出现的次数.png
计算给定字符串儿中特定字符出现的次数.png

  解决这个问题也不是太难,最常规的做法就是使用for...in循环进行遍历,找出所有的特定字符,然后再将结果进行返回:

// 常规的解决方案
func challenge(input: String, character: Character) -> Int {
    
    // 定义一个变量,用来记录特定字符的数量
    var letterCount = 0
    
    // 遍历字符串儿中所有的字符
    for letter in input.characters {
        
        // 如果发现特定字符
        if letter == character {
            
            // 数量加1
            letterCount += 1
        }
    }
    
    // 将计算结果返回
    return letterCount
}


let str = "The US State Department on Friday has officially delivered notice to the UN of its intentions to withdraw from the 2015 Paris climate pact."
challenge(input: str, character: "i")  // 结果为11个

  上述问题完美解决。当然,如果仅仅是这样,那就太没意思了!接下来就厉害了,因为我要开始装逼了,而且还是三连发:

// 装逼第一弹
func challenge(input: String, character: Character) -> Int {
    
    // 返回要查找的字符character的数量
    return input.characters.reduce(0) {
        
        // 如果参数$1和要查找的字符character相等,则返回$0加1;否则就直接返回$0
        $1 == character ? $0 + 1 : $0
    }
}

let str = "The US State Department on Friday has officially delivered notice to the UN of its intentions to withdraw from the 2015 Paris climate pact."
challenge(input: str, character: "i")  // 结果为11个

  这段代码是不是比使用for...in循环要简洁许多?但是,简洁的代码一般都不太容易理解,所以接下来我需要对上面的代码做进一步的解释。要想完全理解上面的代码,首先必须知道三个知识点:第一个是reduce(_:_:)函数;第二个则是尾随闭包;第三个则是省略闭包参数

  我们先来解释reduce(_:_:)函数。它接收两个参数,第一个参数initialResult作为初始值,会在nextPartialResult这个闭包首次执行时传递给它;而第二个参数,也就是nextPartialResult这个闭包,其作用是将累加值和序列中的元素组合成一个新的累加值,并且在nextPartialResult闭包再次调用时,将这个新的累加值返回给调用者。reduce(_:_:)函数的返回值就是最终累加的值。当然,如果序列中没有任何元素,那么它的返回值就是initialResult。

  第二个需要了解的概念是尾随闭包。所谓的尾随闭包,就是指当一个函数接收多个参数,并且最后一个参数是一个闭包时,可以将这个闭包参数写在函数小括号后面。详细解释参见我的另一篇笔记《Swift中的闭包》。搞清楚上面这个两个概念之后,为了便于理解,我们可以把上面的代码完整的写出来:

func challenge(input: String, character: Character) -> Int {
    return input.characters.reduce(0, { (num: Int, char: Character) -> Int in
        char == character ? num + 1 : num
    })
}

  因为我们要计算字符串儿str中所有字符i的数量,所以最开始计数时其个数应该为0,这也就是为什么reduce(_:_:)函数的第一个参数值为0的原因。接下来,我们详细解释一下reduce(_:_:)函数的第二个参数,也就是好大一坨的那个闭包。闭包第一个参数num表示特定字符char的个数,第二个参数char表示要计算的特定字符。当char和character相等时(也就是找到特定的字符),我们就让num的值加1,否则就直接返回num的值。

  最后再来说一下省略闭包参数。一般情况下,如果闭包中有多个参数,我们可以使用$0、$1、$2、... 、$n来表示这些参数,其中$0就表示闭包中的第一个参数,而$1就表示闭包中的第二个参数...依次类推。因此,上面的代码可以表示为:

func challenge(input: String, character: Character) -> Int {
    
    return input.characters.reduce(0, {$1 == character ? $0 + 1 : $0})
}

  如果再结合尾随闭包的知识,那么它就可以写成我们最开始的样子了。不过,需要说明的是,使用reduce(_:_:)函数倒是书写方便了,不过性能上要比直接使用for...in循环差一些。正所谓装逼是有代价的。

  一般情况下,装完逼就应该赶紧跑,但是我岂是那样的人?我得留下来继续装逼。所以,装逼第二弹来了:

func challenge(input: String, character: String) -> Int {

    // 将输入的每个字符转换成字符串儿,并且创建字符串儿数组
    let array = input.characters.map { String($0) }

    // 用字符串儿数组array创建一个可计数集合
    let counted = NSCountedSet(array: array)

    // 计算character重复出现的次数,并且将结果返回
    return counted.count(for: character)
}

let str = "The US State Department on Friday has officially delivered notice to the UN of its intentions to withdraw from the 2015 Paris climate pact."
challenge(input: str, character: "i")  // 结果为11个

  上面这段代码理解起来比使用reduce(_:_:)函数那个要简单一些。主要知识点就在于map(_:)函数和NSCountedSet这个类。map(_:)函数的参数transform是一个映射闭包, 而这个闭包又接收来自序列中的元素作为其参数,闭包对参数进行某种变换之后,再将其作为相同类型或者不同类型的变换值返回出去。整个map(_:)函数的返回值是一个包含此序列的转换元素的数组。就以我们上面的代码而言,我们将字符串儿str中的第一个字符强制转换成String类型(未转换之前,str中单个的字符是Character类型),那么最后的结果就是str中所有的字符都会被转换成String类型,并且存放在一个[String]类型的数组中。NSCountedSet的作用是将[String]类型的数组array变成一个可计数的集合,而这个集合中有一个count(for: )方法,它可以计算出一个可计数的集合中有多少个重复的指定字符。为了便于理解,我还是将map(_:)函数部分完整的写出来:

func challenge(input: String, character: String) -> Int {

    // 将输入的每个字符转换成字符串儿,并且创建字符串儿数组
    let array = input.characters.map { (char: Character) -> String in
        
        // 将字符char从Character类型强制转换成String类型,并且返回
        return String(char)
    }

    // 用字符串儿数组array创建一个可计数集合
    let counted = NSCountedSet(array: array)

    // 计算character重复出现的次数,并且将结果返回
    return counted.count(for: character)
}

  需要说明的是,使用map(_:)函数这种解决方案,其性能比使用reduce(_:_:)函数那个还要差(这个可以通过Playground进行查看),所以,这个也是拿来装逼用的。

  我们搞了那么久,难道就是为了装逼吗?有没有既简洁,又性能不错的解决方案呢?当然没有了!惊不惊喜?意不意外?…… 其实还是有的,我们可以考虑把字符串儿str里面所有包含指定的字符character都删掉,然后再将新字符串儿的长度和原始字符串儿str的长度进行比较,其结果就是我们想要的答案:

func challenge(input: String, character: String) -> Int {

    // 将字符串儿中所有的character字符替换成空的,并且返回一个新的字符串儿
    let modified = input.replacingOccurrences(of: character, with:"")

    // 原始字符串儿的个数再减去新字符串儿的个数,其结果就是所有character字符的个数
    return input.characters.count - modified.characters.count
}

let str = "The US State Department on Friday has officially delivered notice to the UN of its intentions to withdraw from the 2015 Paris climate pact."
challenge(input: str, character: "i")  // 结果为11个

  最后主要是用到了字符串儿的replacingOccurrences(of: with: )方法,它可以用来替换指定的子字符串儿,并且返回一个全新的字符串儿。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值