swift - reduce简介

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]

swift - reduce简介_第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

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

你可能感兴趣的:(Swift,iphone,swift,开发语言,ios)