Swift5.x 范型

//
//  ViewController15.swift
//  swiftT
//
//  Created by  on 2020/6/1.
//  Copyright © 2020  All rights reserved.
//

import UIKit

class ViewController15: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        var stackOfStrings = Stack<String>()
        stackOfStrings.push(item: "Jarvis")
        stackOfStrings.push(item: "Harvis")
        stackOfStrings.push(item: "Marvis")
        print(stackOfStrings)


    }

}


//范型, 函数结构相同的不同参数类型可以抽象成范型函数

//为什么需要范型
// swapTwoInts(_: ,_:) 是一个标准的非范型函数,用于交换两个Int值
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let tempPoraryA = a
    a = b
    b = tempPoraryA
}

// 如果需要交换 两个 String, 两个Double ,上述方法明显不适用了,需要分别写两个不同类型的交换函数。但是范型函数可以做到

//定义一个范型函数  inout ,参数会在函数内直接被修改
func swapTwoValues<T>(_ a: inout T, _ b: inout T ) {
    let tempValue = a
    a = b
    b = tempValue
}


// Swift 里定义范型类型
// Int Stack
struct IntStack {
    var items = [Int]()
    mutating func push(_ item: Int){
        items.append(item)
    }
    
    mutating func pop() -> Int {
        return items.removeLast()
    }
}

// 范型 Stack
struct Stack<Element> {
    var items = [Element]()
    mutating func push(item: Element) {
        items.append(item)
    }
    
    mutating func pop() -> Element {
        return items.removeLast()
    }
}

// 扩展范型类型
// 当扩展一个范型类型时,不需要在扩展的定义中提供类型形式参数列表。原始类型定义的类型形式参数列表在扩展体里仍然有效。并且原始类型形式参数列表名称也用于扩展类型形式参数
extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

// 如何为 Swift 里的类型约束
// swapTwoValues(_: _:)函数和Stack 类型可以用于任意类型。但是,有时在用于范型函数的类型和范型类型上,强制其遵循特定的类型约束很有用。类型约束指出一个类型形式参数必须继承自特定类,或者遵循一个特定的协议、组合协议
// 例如: Swift的Dictionary类型在可用于字典中键的类型上设置类一个限制。如字典中描述的一样。字典键的类型必须时可哈希的。也就是说,它必须提供一种使其可以唯一表示的方法。Dictonary需要它的键是可哈希的,以便它可以检查字典中是否包含一个特定键的值。没有了这个要求,Dictionary不能区分该插入是替换一个指定的键的值,也不能在字典中查找已经给定的键的值。

// 定义一个类型约束语法
// 在一个类型形式参数名称后面放置一个类或者协议作为形式参数列表的一部分,并用冒号隔开,以写出一个类型约束。
func someFunc<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    //
}

// 类型约束的应用
// 一个非范函数:在给定的String值数组中查找给定的String值,findIndex(ofIndex: in:) 函数返回一个可选的Int值,如果找到了给定字符串,它会返回数组中第一个匹配的字符串的索引值,如果站不到给定字符串就返回nil
func findIndex(ofString valueToFInd: String, in array: [String]) -> Int? {
    for(index, value) in array.enumerated() {
        if value == valueToFInd {
            return index
        }
    }
    return nil
}


// findIndex的范型版本,findIndex(of: in:)。
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
    for(index, value) in array.enumerated() {
        if value == valueToFind {// 不加Equatable, 会报错,Binary operator '==' cannot be applied to two 'T' operands
            return index
        }
    }
    return nil
}

//编译不通过  的原因是范型类型不是科比较的。需要遵循其协议的类型要实现相等操作符(==)和不等操作符(!=)用于比较该类型的任意两个值,所有Swift标准库中的类型自动支持 Equatable 协议
// 任何Equatable的类型都能安全地用于 findIndex(of: in:) 函数,因为可以保证那些类型支持相等操作符。

//关联类型
//定义一个协议时, 有时在协议定义里声明一个或多个关联类型时很有用的。关联类型给协议中用到的类型一个占位符名称。直到采纳协议时,才指定用于该关联类型的实际类型。关联类型通过associatedtype关键字指定

//关联类型的应用
//1、这个协议没有指定元素如何存储在容器中,也没有指定允许存入容器的元素类型,协议仅仅指定了想成为一个container的类型,必须提供的三种功能。遵循该协议的类型可以提供其他功能,只要满足这三个要求即可。
//2、任何中心container协议的类型必须指定其存储值的类型。尤其是它必须保证只有正确类型的元素才能添加到容器中,而且该类型下标返回的元素类型必须是正确的
//3、为了定义这些要求,Container协议需要在一种不知道容器类型的情况下,引用该容器将存储的元素类型的方法。Container协议需要指定所有传给append(_:)方法的值必须和容器里元素的值是一样的,而且容器下标返回的值也是和容器里元素的值类型相同

protocol Container {
    associatedtype ItemType
    mutating func append(_ item: ItemType)
    var count: Int{ get }
    subscript(i: Int) -> ItemType {get}
}


// 整型Stack 关联对象
struct IntStackT: Container {
    // original IntStackT<Int>
    var items = [Int]()
    mutating func push(_ item: Int){
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // conform to the Container protocol
    typealias ItemType = Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int{
        return items[i]
    }
}

struct StackT<Element>: Container {
    var items = [Element]()
    mutating func push(_ item: Element){
        items.append(item)
    }

    mutating func pop() -> Element {
        return items.removeLast()
    }
    // confirm to the Container protocol
//    typealias ItemType = Element
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}


//可以在协议里给关联类型添加约束来要求遵循的类型满足约束
protocol ContainerE {
    associatedtype ItemType: Equatable// 给关联类型添加 Equatable 约束
    mutating func append(_ item: ItemType)
    var count: Int{ get }
    subscript(i: Int) -> ItemType {get}
}

//协议可以作为它自身的要求出现
protocol SuffixableContainer: Container {
    associatedtype Suffix: SuffixableContainer where Suffix.ItemType == ItemType
    func suffix(_ size: Int) -> Suffix
}

//范型的 where子句
//如类型约束一样,范型约束允许你在范型函数或范型类型相关的类型形式参数上定义要求
//类型约束在为关联类型定义约束时也很有用。通过定义一个范型where子句来实现。范型Where子句让你能够要求一个关联类型必须遵循指定的协议,或者指定的类型形式参数和关联类型必须相同。范型where子句以where关键字开头,后接关联类型的约束或类型和关联x类型一致的关系。范型子句在一个类型或者函数体的左个大半括号前面。
// 例子
// C1 必须要遵循Container协议 (写作 C1: Container)
// C2 也必须遵循Container洗衣 (写作 C2: Container)
// C1的ItemType必须和C2的ItemType相同 (写作 C1.ItemType == C2.ItemType)
// C1的ItemType必须遵循Equatable协议 (写作 C1.ItemType:Equatable)
func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
    //check that both containers contain the same number of items
    if someContainer.count != anotherContainer.count {
        return false
    }
    
    //check eatch pair of items to see if they're equivalent
    for i in 0..<anotherContainer.count {
        if someContainer[i] != anotherContainer[i] {
            return false
        }
    }
    
    //all items match, so return true
    return true
}

//带有范型 where子句的扩展
// 你同时也可以使用范型的where子句来作为扩展的一部分
extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

// 关联类型的范型where子句
// 关联类型中包含一个范型where子句。比如说,一个包含遍历器的ContainerF,比如标准库中Sequence协议
protocol ContainerF {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int{ get }
    subscript(i: Int) -> Item{ get }
    associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
    func makeIterator() -> Iterator
    
}

//protocol ComparableContainer: Container where Item: Comparable {}

//范型下标
// 下标可以时范型,它们可以包含范型where子句,可以在subscript后用尖括号来写类型占位符,还可以在下标代码块花括号前写范型where子句
//1、在尖括号中的范型形式参数Indices必须遵循标准库中Sequence协议的某类型
//2、下标接收单个形式参数,indices, 它是一个Indices类型实例
//3、范型where子句要求序列的便利器必须便利Int类型的元素。这就保证了序列中的索引都是作为容器索引的相同类型。
//4、合在一起,这些限定意味着传入的indices 形式参数是一个整数的序列
extension Container {
    subscript<Indices: Sequence>(indices: Indices) -> [ItemType] where Indices.Iterator.Element == Int{
        var result = [ItemType]()
        for index in indices {
            result.append(self[index])
        }
        return result
    }
}


// 范型思维
// 1、面向过程的编程,可以将常用代码段封装在一个函数中,然后通过函数调用来达到目标代码重用的目的。
// 2、面向对象的方法,则可以通过类的继承来实现(对象的目标)代码重用
//如果需要写一个用于不同数据类型的算法,可以采用的方法有
//1、面向过程: 对原代码进行复制和修改。生成不同数据类型版本的算法函数,调用时需要对数据类型进行手工的判断
//2、面向对象: 可以在一个类中,编写多个同名函数,他们呢的算法一致,但是所处理数据类型不同。当然函数的输入参数类型也不同,可通过函数重载来自动调用对应数据类型版本的函数