seq.sorted(_:)

sort a sequence on multiple properties

ๆœ‰ไบ†ไธ‹้ข็š„ๆ“ดๅ……ๅพŒ๏ผŒๆˆ‘ๅ€‘ๅฐฑๅฏไปฅ็”จ้€™้บผๆผ‚ไบฎ็š„่ชžๆณ•ไพ†ๅฐ็‰ฉไปถๆŽ’ๅบ๏ผš

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

ๅ–„็”จ guard ... else ๅฏไปฅ่ฎ“ใ€Œ็”จๅคšๅ€‹ๅฑฌๆ€งไพ†ๆŽ’ๅบใ€็š„่ชžๆณ•่ฎŠๅพ—ๆ›ด็ฐกๆฝ”ใ€‚

// 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
        }
    }
}

Last updated