reduce
减少,降低;(烹调中)使变浓稠,收汁;<美>节食减肥;使沦为,使陷入(不好的境地);迫使,使不得不(做);(通过破裂、燃烧等)使变成,使化为;归纳,简化;将分数约到(最小项);(使)进行还原反应;减薄(底片或图片);(语音)弱化;使(脱臼,断骨)复位;<古>攻克,征服(尤指围攻并占领城镇或要塞)
基础:
reduce函数是,一个可以设置一个初始值的函数,并且可以返回两个结果变量,我们一般称为result,currentCase
result: 一般是指上次得到的结果之和
currentCase: 一般指本次遍历的对象
举例:
let prices = [20,30,40]
let sum = prices.reduce(0) { $0 + $1 }
print(sum)
//90
注意: reduce(0) 这里reduce(0)是什么初始值,我们函数就会返回什么结果。
let prices = [20,30,40]
let sum = prices.reduce(100) { $0 + $1 }
print(sum)
//190
计算数组元素的出现次数
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属性的总和
我们想要获得学生年龄的总和
这个就比较简单了,单纯的年龄相加就可以了,代码如下:
let result = students.reduce(0) {
$0 + $1.age
}
print(result) // 131
对于数组元素类型是支持加法运算符 ( +) 的类型,我们可以通过省略简写参数名称来进一步简化它:
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 名学生的方法:
// 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)
这种方法的一个好处是,即使我们尝试获取的元素数量超过数组的总元素,它也不会触发索引超出范围异常。
结果
[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的第一个字母对学生进行分组。传统上,我们必须手动遍历数组中的每个元素, 并相应地对它们进行分组。
struct Student {
let id: String
let name: String
let gender: Gender
let age: Int
}
现在,在Dictionary(grouping:by:)初始化程序的帮助下,我们可以在不使用for-in循环的情况下实现这一点。就是这样:
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 中使用速记参数名称或者健路语法来进一步简化
// 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到另一个对象中,示例:
如下有一个数组,把偶数放一个数组中,把奇数放一个数组中:
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:参数,本例中即二维数组)
func reduce<Result>(into: Result, _ updateAccumulatingResult: (inout Result, Element) throws -> ()) -> Result
为了更方例理解,展开为:
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
再来看一个示例:
// 统计下面的字符串中每个字符的使用次数
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]
或者
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:
$0[$1, default: 0] += 1
使用了 Swift 字典的子脚本支持。其中 default: 关键字允许当字典中不存在键时返回一个默认值。
那么为什么要用 default: 呢?因为在统计的时候,字母第一次出现时字典中并没有对应键。如果直接写成:
$0[$1] += 1
首次出现时因为字典没有这个键,会导致崩溃。
使用 default: 可以确保每次统计都至少从 0 开始,不会因为 KeyError 崩溃。
那么它具体的作用就是:
第一次出现该字母时,会返回默认值 0,并把对应键值对添加入字典
后续出现该字母时,直接累加计数器
所以 default: 保证了程序的鲁棒性,可以正常统计首次出现的字母,避免崩溃。
另一种写法是使用 nil 合并运算符:
$0[$1] = ($0[$1] ?? 0) + 1
也可以避免首次出现时的崩溃。
但使用 default: 更简洁清晰一些。
还有个示例:
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秒的时间内可获取的奖励的数量
struct RewordModel {
let time: Int
let reward: Int
}
let data: [RewordModel] = [
RewordModel(time: 20, reward: 50),
RewordModel(time: 30, reward: 100),
RewordModel(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)
参考摘录:
https://juejin.cn/post/6983286929882087454
https://www.jianshu.com/p/781d5f6020b3
https://juejin.cn/post/7066782801928044581