swift - reduce简介

news/2024/7/20 21:19:08 标签: swift, 开发语言, ios

reduce

减少,降低;(烹调中)使变浓稠,收汁;<美>节食减肥;使沦为,使陷入(不好的境地);迫使,使不得不(做);(通过破裂、燃烧等)使变成,使化为;归纳,简化;将分数约到(最小项);(使)进行还原反应;减薄(底片或图片);(语音)弱化;使(脱臼,断骨)复位;<古>攻克,征服(尤指围攻并占领城镇或要塞)

基础:

reduce函数是,一个可以设置一个初始值的函数,并且可以返回两个结果变量,我们一般称为result,currentCase

result: 一般是指上次得到的结果之和

currentCase: 一般指本次遍历的对象

举例:

swift">let prices = [20,30,40]
let sum = prices.reduce(0) { $0 + $1 }
print(sum)

//90

注意: reduce(0) 这里reduce(0)是什么初始值,我们函数就会返回什么结果。

swift">let prices = [20,30,40]
let sum = prices.reduce(100) { $0 + $1 }
print(sum)

//190

计算数组元素的出现次数

swift">let students = [
    Student(id: "991", name: "Jessica", gender: .female, age: 20),
    Student(id: "992", name: "James", gender: .male, age: 25),
    Student(id: "993", name: "Mary", gender: .female, age: 19),
    Student(id: "994", name: "Edwin", gender: .male, age: 27),
    Student(id: "995", name: "Stacy", gender: .female, age: 18),
    Student(id: "996", name: "Emma", gender: .female, age: 22),
]

enum Gender {
    case male
    case female
}

struct Student {
    let id: String
    let name: String
    let gender: Gender
    let age: Int
}

let result = students.reduce(into: (male: 0, female: 0)) {
// 或者
// let result = students.reduce(into: (0, 0)) {
    if $1.gender == .male {
        $0.0 += 1
    } else {
        $0.1 += 1
    }
}

print("male: result \(result.0) female: \(result.1)")
print("male: result \(result.male) female: \(result.female)")

获取数组内某个model属性的总和

我们想要获得学生年龄的总和

这个就比较简单了,单纯的年龄相加就可以了,代码如下:

swift">let result = students.reduce(0) {
    $0 + $1.age
}

print(result) // 131

对于数组元素类型是支持加法运算符 ( +) 的类型,我们可以通过省略简写参数名称来进一步简化它:

swift">let sum1 = [2, 3, 4].reduce(0, +)          // Output: 9
let sum2 = [5.5, 10.7, 9.43].reduce(0, +)  // Output: 44.435
let sum3 = ["a","b","c"].reduce("", +)     // Output: "abc"

从数组中获取一些随机元素

从数组中获取一些随机元素曾经是一种难以实现的算法,因为我们需要处理各种边缘情况。
现在借助Swift数组中的shuffled() 和 prefix(_:)函数,这个操作变得非常容易实现。
以下是从students数组中随机挑选 3 名学生的方法:

swift">// Randomize all elements within the array
let randomized = students.shuffled()

// Get the first 3 elements in the array
let selected = randomized.prefix(3)
let selected2 = randomized.prefix(13)
print(selected)
print(selected2)

这种方法的一个好处是,即使我们尝试获取的元素数量超过数组的总元素,它也不会触发索引超出范围异常。
结果

swift">[SwiftUnitTest.Student(id: "996", name: "Emma", gender: SwiftUnitTest.Gender.female, age: 22), 
SwiftUnitTest.Student(id: "991", name: "Jessica", gender: SwiftUnitTest.Gender.female, age: 20), 
SwiftUnitTest.Student(id: "992", name: "James", gender: SwiftUnitTest.Gender.male, age: 25)]


[SwiftUnitTest.Student(id: "996", name: "Emma", gender: SwiftUnitTest.Gender.female, age: 22), 
SwiftUnitTest.Student(id: "991", name: "Jessica", gender: SwiftUnitTest.Gender.female, age: 20),
SwiftUnitTest.Student(id: "992", name: "James", gender: SwiftUnitTest.Gender.male, age: 25), 
SwiftUnitTest.Student(id: "995", name: "Stacy", gender: SwiftUnitTest.Gender.female, age: 18), 
SwiftUnitTest.Student(id: "993", name: "Mary", gender: SwiftUnitTest.Gender.female, age: 19), 
SwiftUnitTest.Student(id: "994", name: "Edwin", gender: SwiftUnitTest.Gender.male, age: 27)]

我们可以看到随机可以随到最大的数6就结束了,并没有一定要完成到 13

按条件对数组元素进行分组

假设我们想按student name的第一个字母对学生进行分组。传统上,我们必须手动遍历数组中的每个元素, 并相应地对它们进行分组。

swift">struct Student {
    let id: String
    let name: String
    let gender: Gender
    let age: Int
}

现在,在Dictionary(grouping:by:)初始化程序的帮助下,我们可以在不使用for-in循环的情况下实现这一点。就是这样:

swift">let groupByFirstLetter = Dictionary(grouping: students) { student in
    return student.name.first!
}

/*
 PrintLog:
 [
    ["J": [SwiftUnitTest.Student(id: "991", name: "Jessica", gender: SwiftUnitTest.Gender.female, age: 20), SwiftUnitTest.Student(id: "992", name: "James", gender: SwiftUnitTest.Gender.male, age: 25)],
    "M": [SwiftUnitTest.Student(id: "993", name: "Mary", gender: SwiftUnitTest.Gender.female, age: 19)], 
    "E": [SwiftUnitTest.Student(id: "994", name: "Edwin", gender: SwiftUnitTest.Gender.male, age: 27), SwiftUnitTest.Student(id: "996", name: "Emma", gender: SwiftUnitTest.Gender.female, age: 22)], 
    "S": [SwiftUnitTest.Student(id: "995", name: "Stacy", gender: SwiftUnitTest.Gender.female, age: 18)]]

 ]
 */

从上面的示例代码中可以看出,初始化程序将生成一个类型为 的字典[KeyType: Student]。
KEY = 每个Student 的name 的第一个字母 ,
如果我们想按标准对学生进行分组, 并在多部分表格视图中显示他们,这将特别有用。
我们甚至可以通过Swift 中使用速记参数名称或者健路语法来进一步简化

swift">// Using shorthand argument names
let groupByFirstLetter = Dictionary(grouping: students, by: { $0.name.first! })

// Using key path syntax
let groupByFirstLetter = Dictionary(grouping: students, by: \.name.first!)

reduce(into:)

reduce(into:)方法也是一个实用方法,主要作用是遍历数组中的元素,把它们into到另一个对象中,示例:

如下有一个数组,把偶数放一个数组中,把奇数放一个数组中:

swift">let nums = [1,2,3,4,5]
let result = nums.reduce(into: [[],[]]) { arr, num in
    arr[num%2].append(num)
}
//或者
let result = nums.reduce(into: [[],[]]) {
    $0[$1%2].append($1)
}
print(result[0]) // [2, 4]
print(result[1]) // [1, 3, 5]

这里into:后面的[[], []]是一个二级数组,这个二维数组即闭包中的arr, 而闭包中的num是nums数组中每一个值,遍历后把这个二维数组返回 (由函数原型的inout可知,返回的其实就是into:参数,本例中即二维数组)

swift">func reduce<Result>(into: Result, _ updateAccumulatingResult: (inout Result, Element) throws -> ()) -> Result

为了更方例理解,展开为:

swift">temp[1].append(1) //1%2 = 1/2 left 1 [[][1]]
temp[0].append(2) //2%2 = 2/2 left 0 [[2][1]]
temp[1].append(3) //3%2 = 3/2 = 1 left 1 [[2][1,3]]
temp[0].append(4) //4%2 = 4/2 left 0 [[2,4][1,3]]
temp[1].append(5) //5%2 = 5/2 = 2 left 1 [[2,4][1,3,5]]

示例参考:https://stackoverflow.com/questions/62103658/how-do-you-use-reduceinto-in-swift

再来看一个示例:

swift">// 统计下面的字符串中每个字符的使用次数
let letters = "abracadabra"
let letterCount = letters.reduce(into: [:]) { counts, letter in
    counts[letter, default: 0] += 1
}
print(letterCount) // ["a": 5, "r": 2, "c": 1, "b": 2, "d": 1]

或者

swift">let letters = "abracadabra"
let letterCount = letters.reduce(into:[:]) {
	//$0[$1] = ($0[$1] ?? 0) +1 
	$0[$1, default: 0] += 1
}
print(letterCount) // ["a": 5, "r": 2, "c": 1, "b": 2, "d": 1]

在这里插入图片描述

其实就是遍历字符串,然后把它们into到字典[:]中

讲解为啥使用default:

swift">$0[$1, default: 0] += 1

使用了 Swift 字典的子脚本支持。其中 default: 关键字允许当字典中不存在键时返回一个默认值。
那么为什么要用 default: 呢?因为在统计的时候,字母第一次出现时字典中并没有对应键。如果直接写成:

swift">$0[$1] += 1

首次出现时因为字典没有这个键,会导致崩溃。
使用 default: 可以确保每次统计都至少从 0 开始,不会因为 KeyError 崩溃。
那么它具体的作用就是:
第一次出现该字母时,会返回默认值 0,并把对应键值对添加入字典
后续出现该字母时,直接累加计数器
所以 default: 保证了程序的鲁棒性,可以正常统计首次出现的字母,避免崩溃。
另一种写法是使用 nil 合并运算符:

swift">$0[$1] = ($0[$1] ?? 0) + 1

也可以避免首次出现时的崩溃。
但使用 default: 更简洁清晰一些。

还有个示例:

swift">struct Person {
    enum Gender {
        case male
        case female
    }
    
    var name = ""
    var age = 0
    var gender = Gender.female
}

let dataSource = [Person(name: "鸡大宝", age: 38, gender: .male),
                  Person(name: "江主任", age: 50, gender: .female),
                  Person(name: "可乐", age: 10, gender: .female),
                  Person(name: "伍六七", age: 16, gender: .male),
                  Person(name: "梅花十三", age: 20, gender: .female)]

// 获取数据源中男女各多少人
let genderCount = dataSource.reduce(into: [Person.Gender: Int]()) { result, person in
    result[person.gender, default: 0] += 1
}
let maleCount = genderCount[Person.Gender.male] // 2
let femaleCount = genderCount[Person.Gender.female] // 3
print(maleCount ?? 0) // 2
print(femaleCount ?? 0) // 3

根据内容指定条件下数值的相加

例如下面的例子,获取100秒的时间内可获取的奖励的数量

swift">struct Model {
	let time: Int
    let reward: Int
}
    
let data: [Model] = [
	Model(time: 20, reward: 50),
    Model(time: 30, reward: 100),
    Model(time: 70, reward: 200),
]
let targetTime = 100
let total = data.reduce(into: (time: 0, amount: 0)) {
	if Int($0.0) + $1.time < targetTime {
    $0.0 += $1.time 
     $0.1 += $1.reward
}
}
print(total)
// 打印 150 (即50+100)

合理使用reduce

例如获取数组中是否所有model的done都为true

swift">struct Model {
	let time: Int
    let reward: Int
    let done: Bool
}    
let data: [Model] = [
	Model(time: 20, reward: 50, done: true),
    Model(time: 30, reward: 100, done: true),
    Model(time: 70, reward: 200, done: false),
    ]

let result = data.reduce(into: true) {
    $0 = $0 && ($1.done == true)
}

这么写会提示如下错误:

swift Reduce Boolean Violation: Use `allSatisfy` instead (reduce_boolean)

原因是:

reduce_boolean: Reduce Boolean  优先使用.allSatisfy()或.contains() 不建议使用:reduce(true)或reduce(false)

所以可将上述代码改为:

swift">let result = !data.contains {
	$0.done == false
}
//或者
let result = data.allSatisfy {
    $0.done == true
}

这么看,是不是contains方法更为简洁方便呢

参考摘录:
https://juejin.cn/post/6983286929882087454
https://www.jianshu.com/p/781d5f6020b3
https://juejin.cn/post/7066782801928044581


http://www.niftyadmin.cn/n/5357055.html

相关文章

两个近期的计算机领域国际学术会议(软件工程、计算机安全):欢迎投稿

近期&#xff0c;受邀担任两个国际学术会议的Special session共同主席及程序委员会成员&#xff08;TPC member&#xff09;&#xff0c;欢迎广大学界同行踊跃投稿&#xff0c;分享最新研究成果。期待这个夏天能够在夏威夷檀香山或者加利福尼亚圣荷西与各位学者深入交流。 SERA…

高频一体式读写器的应用及其原理

高频一体式读写器作为一款读写设备&#xff0c;将RFID读写模块和天线集于一体&#xff0c;通过天线与RFID标签进行无线通信&#xff0c;实现对标签的识别和内存数据的读出或写入操作。具备安全、准确、快速、扩展、兼容性强等特点&#xff0c;具备非接触识别、远距离识别、环境…

【2024程序员必看】鸿蒙应用开发行业分析

鸿蒙操作系统沉浸四年&#xff0c;这次终于迎来了破局的机会&#xff0c;自从2023年华为秋季发布会上宣布鸿蒙 Next操作系统不在兼容Android后&#xff0c;就有不少大厂开始陆续与华为达成了鸿蒙原生应用的开发合作&#xff0c;据1月18日华为官方宣布110多天的产业合力“突进”…

一分钟教程系类之物品抠图方法分享

随着电商行业的不断发展&#xff0c;电商小伙伴们在日常工作中经常需要抠图&#xff0c;尤其是物品抠图。对于一些刚刚入职的小白来说&#xff0c;可能会觉得抠图是一项相当困难的任务&#xff0c;但其实只要掌握了正确的方法&#xff0c;抠图也可以变得轻松简单。下面&#xf…

【Spring连载】使用Spring Data访问Redis(四)----RedisTemplate

【Spring连载】使用Spring Data访问Redis&#xff08;四&#xff09;----RedisTemplate通过RedisTemplate处理对象Working with Objects through RedisTemplate 一、专注String的便利类二、Serializers 大多数用户可能使用RedisTemplate及其相应的包org.springframework.data.r…

TI AM5708工业派

文章目录 一、TI AM5708工业派简介二、主要使用的功能三、J12 扩展接口四、NFS代码实现总结 一、TI AM5708工业派简介 TI AM5708工业派是基于美国德州仪器&#xff08;TI&#xff09;的AM5708处理器所开发的智能硬件工业派&#xff0c;主要面向工业生产、图像处理、智能人机交…

笔记--扩展欧几里得算法

AcWing.877.欧几里得算法 给定 n n n 对正整数 a a ai, b b bi&#xff0c;对于每对数&#xff0c;求出一组 x x xi, y y yi&#xff0c;使其满足 a a ai x x xi b b bi y y yi g c d ( a gcd(a gcd(ai , b ,b ,bi ) ) )。 输入格式 第一行包含整数 n n n。 接下来 …

GO 的 Web 开发系列(四)—— 静态资源文件访问的几种方式

Gin 的路由其实就是 Java 中 RequestMapping 注解的内容&#xff0c;只是 Go 需要在一个代码模块中统一对所有路由进行配置。 全文链接&#xff1a;https://blog.nineya.com/archives/155.html 一、路由组配置 一般情况下&#xff0c;通过路由需要完成两个事情&#xff0c;一…