> For the complete documentation index, see [llms.txt](https://lochiwei.gitbook.io/ios/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lochiwei.gitbook.io/ios/swift/collections/sequence/seq.sorted-_.md).

# seq.sorted(\_:)

sort a sequence on multiple properties

有了下面的擴充後，我們就可以用這麼漂亮的語法來對物件排序：

```swift
let sorted = items.sorted(
    .descending(\.price), 
    .descending(\.rate), 
    .ascending (\.title)
)
```

{% hint style="info" %}
善用 <mark style="color:red;">**guard ... else**</mark> 可以讓「**用多個屬性來排序**」的語法變得更簡潔。
{% endhint %}

{% tabs %}
{% tab title=".sorted()" %}
:point\_right: [replit](https://replit.com/@pegasusroe/seqsorted)

````swift
// 1.0.0: 2021.12.11

/*
    設計理念：
    --------
    我們將要用於排序的屬性 key path (`prop`) 傳入 `SortOrder`
    的 `isBefore` 與 `isEqual` 兩個 closure 中，如此就可以
    直接用 order.isBefore(a, b) 或 order.isEqual(a, b) 來
    比較兩個 Element 的大小，而不需要為了要將這個 key path 存起來
    而傷腦筋到底要用： KeyPath<Element, Property> 還是要用
    PartialKeyPath<Element> 型別？

    事實上，用這兩種型別存起來都有問題。

    首先，如果用 KeyPath<Element, Property>，這時勢必要將 SortOrder
    的型別也改為 SortOrder<Element, Property>，但這樣一改，後面
    sorted() 函數的 variadic 參數 orders 也要跟著改，這會造成
    所有的屬性都是同一種 Property 型別，直接讓 sorted() 函數無法使用。

    再來，如果用 PartialKeyPath<Element> 來存 key path，這時
    這個 key path 會失去 (type-erase) 它的 Value type，間接
    造成系統無法辨別 element[keyPath: keyPath] 的型別，因此也
    不知道原來這個值是一個 Comparable，所以在程式中也沒辦法用 "==",
    "<", ">" 這些運算符號，因此就算將這個 key path 存起來也沒什麼用。
*/

/// SortOrder
public struct SortOrder<Element> {
    public let isBefore: (Element, Element) -> Bool
    public let isEqual : (Element, Element) -> Bool
}

// ⭐️ SortOrder
extension SortOrder {

    /// .ascending(\.property)
    public static func ascending<Property: Comparable>(
        _ prop: KeyPath<Element, Property>
    ) -> SortOrder<Element> 
    {
        return SortOrder(
            isBefore: { $0[keyPath: prop] <  $1[keyPath: prop] },
            isEqual : { $0[keyPath: prop] == $1[keyPath: prop] }
        )
    }
    
    /// .descending(\.property)
    public static func descending<Property: Comparable>(
        _ prop: KeyPath<Element, Property>
    ) -> SortOrder<Element> 
    {
        return SortOrder(
            isBefore: { $0[keyPath: prop] >  $1[keyPath: prop] },
            isEqual : { $0[keyPath: prop] == $1[keyPath: prop] }
        )
    }
}

// seq.sorted(_:)
extension Sequence {
    /// ⭐️ items in a `Sequence` sorted by multiple properties.
    /// example:
    /// ```
    /// items.sorted(.ascending(\.title))
    /// items.sorted(.ascending(\.price), .descending(\.rate))
    /// ```
    public func sorted(_ orders: SortOrder<Element>...) -> [Element] {
        return sorted { a, b in
            // ⭐️ if some property is not equal, return the order
            for order in orders {
                guard order.isEqual(a, b) 
                else { return order.isBefore(a, b) }
            }
            // ⭐️ all properties are equal, so `a` is NOT before `b`
            return false
        }
    }
}
````

{% endtab %}

{% tab title="💈範例" %}

```swift
// Using extension
struct Post {
    let title: String
    let views: Int
    let duration: Int
}

var posts = [
    Post(title: "Alice", views: 1, duration:  3),
    Post(title: "Beth" , views: 1, duration:  1),
    Post(title: "Cloe" , views: 1, duration:  2),
    Post(title: "David", views: 5, duration:  2),
    Post(title: "Ethan", views: 4, duration: 10),
    Post(title: "Felix", views: 5, duration:  2)
]

// ⭐️ seq.sorted(_:)
let sorted = posts.sorted(
    .descending(\.views), 
    .descending(\.duration), 
    .ascending (\.title)
)
// [
//     Post(title: "David", views: 5, duration:  2), 
//     Post(title: "Felix", views: 5, duration:  2), 
//     Post(title: "Ethan", views: 4, duration: 10), 
//     Post(title: "Alice", views: 1, duration:  3), 
//     Post(title: "Beth" , views: 1, duration:  2), 
//     Post(title: "Cloe" , views: 1, duration:  1)
// ]
```

{% endtab %}

{% tab title="💈範例" %}
:point\_right: [replit](https://replit.com/@pegasusroe/sort-on-multiple-properties#main.swift)

```swift
// Without using extension
struct Post {
    let title: String
    let views: Int
    let duration: Int
}

var posts = [
    Post(title: "Alice", views: 1, duration: 3),
    Post(title: "Beth", views: 1, duration: 2),
    Post(title: "Cloe", views: 1, duration: 1),
    Post(title: "David", views: 5, duration: 2),
    Post(title: "Ethan", views: 4, duration: 10),
    Post(title: "Felix", views: 5, duration: 2)
]

// ------------ main ------------

// sort in place
posts.sort {  // $0 is before $1 ?
    
    // check on `views` first
    guard $0.views == $1.views else {
        return $0.views > $1.views          // decreasing
    }

    // $0.views == $1.views
    // check on `duration` next
    guard $0.duration == $1.duration else {
        return $0.duration > $1.duration    // decreasing
    }

    // $0.views    == $1.views
    // $0.duration == $1.duration 
    // check on `title` last
    return  $0.title < $1.title             // increasing
}

print(posts)
// [
//     Post(title: "David", views: 5, duration: 2), 
//     Post(title: "Felix", views: 5, duration: 2), 
//     Post(title: "Ethan", views: 4, duration: 10), 
//     Post(title: "Alice", views: 1, duration: 3), 
//     Post(title: "Beth" , views: 1, duration: 2), 
//     Post(title: "Cloe" , views: 1, duration: 1)
// ]
```

{% endtab %}

{% tab title="📗 參考" %}

* [x] Sundell ⟩&#x20;
  * [x] Sorting Swift Collections ⟩ [Sorting on multiple properties](https://www.swiftbysundell.com/articles/sorting-swift-collections/#sorting-on-multiple-properties)  ⭐️
  * [x] [The power of key paths in Swift](https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift/)
* [ ] Sarunw ⟩&#x20;
  * [ ] [Sort array of objects by multiple properties with Swift Tuple](https://sarunw.com/posts/sort-array-of-objects-by-multiple-properties-with-swift-tuple/)
  * [x] [How to sort by multiple properties in Swift](https://sarunw.com/posts/how-to-sort-by-multiple-properties-in-swift/)
* [x] Web Dev N⟩otes ⟩ [sort by multiple keys](https://lochiwei.gitbook.io/web/appendix/algorithms/sort/sort-by-multiple-keys)
* [ ] KeyPathKit ⟩ [.sort(by:)](https://github.com/vincent-pradeilles/KeyPathKit#sort) , [source code](https://github.com/vincent-pradeilles/KeyPathKit/blob/master/Sources/Operators/sorted.swift) ⭐️⭐️⭐️

  ```
  contacts.sorted(by: .ascending(\.lastName), .descending(\.age))
  ```

{% endtab %}

{% tab title="👥 相關" %}

* [Key Path](/ios/features/key-path.md)
* [sort using key paths](/ios/algorithms/sort/sort-using-key-paths.md)
  {% endtab %}
  {% endtabs %}
