Swift 实现对象排序的Comparable协议
Swift 实现对象排序的Comparable协议

Swift 实现对象排序的Comparable协议

在 Swift 中,Comparable 协议是一种用于实现对象排序的协议。通过让类型遵守 Comparable,可以实现常见的比较运算符(例如 <、<=、> 和 >=)的功能。

本篇作为基础的知识拓展文章,如果不理解使用Comparable协议的场景,可以看一下《Swift通过实现Comparable协议定义比较规则》这篇文章。

协议定义

Comparable 是一个继承自 Equatable 的协议,因此必须实现 == 运算符,还需要实现以下核心方法:

public protocol Comparable: Equatable {
    static func < (lhs: Self, rhs: Self) -> Bool
}

Equatable 的要求

需要实现 == 运算符,表示两个值是否相等。

Comparable 的要求

需要实现 < 运算符,表示第一个值是否小于第二个值。

一旦实现了 <,Swift 会自动为该类型生成其他比较运算符(<=、> 和 >=)。

如何遵守 Comparable 协议

以下是一个简单示例,定义一个 User 类型并使其遵守 Comparable 协议:

struct User: Comparable {
    var firstName: String
    var lastName: String

    // 实现 Equatable 的要求
    static func == (lhs: User, rhs: User) -> Bool {
        lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName
    }

    // 实现 Comparable 的要求
    static func < (lhs: User, rhs: User) -> Bool {
        if lhs.lastName == rhs.lastName {
            return lhs.firstName < rhs.firstName
        } else {
            return lhs.lastName < rhs.lastName
        }
    }
}

代码解释

1、== 运算符

确保 User 类型的两个实例只有在 firstName 和 lastName 都相同时才被认为是相等的。

2、< 运算符

如果 lastName 相同,则比较 firstName。

如果 lastName 不同,则以 lastName 为主进行排序。

使用 Comparable 排序

遵守 Comparable 协议的类型可以直接用于排序操作,例如:

let users = [
    User(firstName: "Arnold", lastName: "Rimmer"),
    User(firstName: "Kristine", lastName: "Kochanski"),
    User(firstName: "David", lastName: "Lister"),
]

let sortedUsers = users.sorted() // 自动按 Comparable 协议定义的规则排序

for user in sortedUsers {
    print("\(user.lastName), \(user.firstName)")
}

输出结果:

Kochanski, Kristine
Lister, David
Rimmer, Arnold

注意事项

1、确保比较逻辑一致性

Comparable 中的 < 实现应该与 Equatable 中的 == 一致。如果 a == b 为 true,则 a < b 和 b < a 都应该为 false。

2、自定义排序逻辑

如果不想实现 Comparable 协议,也可以通过 sorted(by:) 使用自定义的闭包排序。

let sortedUsers = users.sorted {
    if $0.lastName == $1.lastName {
        return $0.firstName < $1.firstName
    } else {
        return $0.lastName < $1.lastName
    }
}

但是可能会存在代码重复等问题,具体细节可见《Swift通过实现Comparable协议定义比较规则》。

3、继承关系

如果类型有继承(例如使用类),确保所有子类的排序逻辑也能正确继承或覆盖。

遵循Equatable协议

前面的代码中提及类型遵循 Comparable 协议时,Swift 隐式要求类型同时遵循 Equatable,而 Swift 会根据类型的存储属性自动合成 Equatable 的实现,只要类型的所有存储属性本身也是 Equatable 的。

// 实现 Equatable 的要求
static func == (lhs: User, rhs: User) -> Bool {
    lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName
}

为什么 Swift 可以自动生成 Equatable?

下面的代码中,Swift结构只遵循Comparable协议,而没有显式声明Equatable协议,但是并没有报错。

struct User: Identifiable, Comparable{
    let id = UUID()
    var firstName: String
    var lastName: String
    static func <(lhs: User, rhs: User) -> Bool {
        lhs.lastName > rhs.lastName
    }
}

UUID 类型和 String 类型都遵循了 Equatable 协议,因此 Swift 能够为 User 自动生成 == 的实现。

自动合成的 == 会逐一比较类型的所有存储属性:当所有存储属性相等时,两个实例被认为相等。

隐式生成的 Equatable 实现

假如 Swift 展开了自动生成的 Equatable 实现,等价于以下代码:

static func ==(lhs: User, rhs: User) -> Bool {
    return lhs.id == rhs.id &&
           lhs.firstName == rhs.firstName &&
           lhs.lastName == rhs.lastName
}

这就是为什么没有显式实现 Equatable,代码仍然可以正常运行。

Comparable 的关系

Comparable 依赖 Equatable,这是因为比较逻辑需要基于某种相等性的判断。例如:

如果 a < b 为 true,a == b 必须为 false。

如果 a == b 为 true,则 a < b 和 b < a 必须都为 false。

因此,Swift 要求所有遵循 Comparable 的类型必须先满足 Equatable 的要求。

如果需要自定义 Equatable

尽管 Swift 自动合成了 Equatable,仍然可以手动实现它,比如让 User 的比较逻辑只依赖于 firstName 和 lastName,而忽略 id:

static func ==(lhs: User, rhs: User) -> Bool {
    return lhs.firstName == rhs.firstName &&
           lhs.lastName == rhs.lastName
}

这样,两个 User 实例的 id 即使不同,只要 firstName 和 lastName 相同,它们仍然会被认为是相等的。

总结

通过实现 Comparable 协议,类型可以更方便地参与排序、搜索等操作,并保持代码的简洁性和一致性。

代码中没有显式实现 Equatable,是因为 Swift 为 User 类型自动生成了 Equatable。

自动生成的 Equatable 会比较类型的所有存储属性。

如果需要自定义 Equatable 的逻辑,完全可以手动实现。

知识扩展

Comparable协议的定义

public protocol Comparable : Equatable {
    static func < (lhs: Self, rhs: Self) -> Bool
    static func <= (lhs: Self, rhs: Self) -> Bool
    static func >= (lhs: Self, rhs: Self) -> Bool
    static func > (lhs: Self, rhs: Self) -> Bool
}

这段代码是Xcodez中关于Comparable的代码定义,隐藏了注释代码。

协议声明
public protocol Comparable : Equatable

Comparable 继承自 Equatable,因此它需要实现 Equatable 的方法(即 == 和 !=)。

另外,它需要实现一个核心函数 static func <,并由此自动获得其余的比较运算符(<=, >, >=)。

核心方法
static func < (lhs: Self, rhs: Self) -> Bool

这是实现 Comparable 协议的唯一必需方法,其作用是:

比较两个值 lhs 和 rhs,如果 lhs 小于 rhs,返回 true;否则返回 false。

这个方法的实现方式由具体类型决定。

这是其他比较运算符(<=, >, >=)的基础,标准库会基于此方法提供其余运算符的默认实现。

示例

struct Person: Comparable {
    let age: Int

    static func < (lhs: Person, rhs: Person) -> Bool {
        return lhs.age < rhs.age
    }
}

let person1 = Person(age: 25)
let person2 = Person(age: 30)
print(person1 < person2)  // true
其他方法

static func <= (lhs: Self, rhs: Self) -> Bool

比较两个值 lhs 和 rhs,如果 lhs 小于或等于 rhs,返回 true;否则返回 false。

默认实现: 标准库会自动实现为:

!(rhs < lhs)

static func >= (lhs: Self, rhs: Self) -> Bool

比较两个值 lhs 和 rhs,如果 lhs 大于或等于 rhs,返回 true;否则返回 false。

默认实现: 标准库会自动实现为:

!(lhs < rhs)

static func > (lhs: Self, rhs: Self) -> Bool

比较两个值 lhs 和 rhs,如果 lhs 大于 rhs,返回 true;否则返回 false。

默认实现: 标准库会自动实现为:

rhs < lhs
为什么只需要实现 <?

Comparable 的设计是为了降低实现成本。开发者只需定义 <,其余操作符都能通过标准库的默认实现得到。

逻辑关系

1、<=: lhs 小于或等于 rhs 等价于 !(rhs < lhs)。

2、>=: lhs 大于或等于 rhs 等价于 !(lhs < rhs)。

3、>: lhs 大于 rhs 等价于 rhs < lhs。

通过这些推导,所有比较操作符都能从 < 中派生。

使用场景

1、排序

如果类型实现了 Comparable,可以直接调用 sort() 和 sorted() 方法。

let numbers = [5, 2, 8, 3]
let sortedNumbers = numbers.sorted()  // [2, 3, 5, 8]

2、最大/最小值

使用 min() 和 max() 获取集合中的最小值或最大值。

let numbers = [5, 2, 8, 3]
print(numbers.min())  // Optional(2)
print(numbers.max())  // Optional(8)

3、范围比较

例如:

if value >= lowerBound && value <= upperBound {
    print("Value is in range")
}
总结

1、Comparable 协议的核心是 < 方法,其余方法基于此方法派生。

2、用途:通过实现 Comparable,可以让类型支持排序、范围比较和集合操作等功能。

3、设计意图:降低实现成本,提高代码的通用性和可读性。

相关文章

Swift通过实现Comparable协议定义比较规则:https://fangjunyu.com/2024/11/21/swift通过实现comparable协议定义比较规则

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注