Swift - จัดเรียงอาร์เรย์ของวัตถุที่มีหลายเกณฑ์


92

ฉันมีอาร์เรย์ของContactวัตถุ:

var contacts:[Contact] = [Contact]()

ติดต่อชั้นเรียน:

Class Contact:NSOBject {
    var firstName:String!
    var lastName:String!
}

และฉันต้องการที่จะเรียงลำดับอาร์เรย์ว่าด้วยการlastNameแล้วโดยในกรณีที่รายชื่อบางส่วนได้เช่นเดียวกันfirstNamelastName

ฉันสามารถจัดเรียงตามหนึ่งในเกณฑ์เหล่านั้น แต่ไม่ใช่ทั้งสองอย่าง

contacts.sortInPlace({$0.lastName < $1.lastName})

ฉันจะเพิ่มเกณฑ์เพิ่มเติมเพื่อจัดเรียงอาร์เรย์นี้ได้อย่างไร


2
ทำแบบเดียวกับที่บอก! รหัสของคุณในวงเล็บปีกกาควรระบุว่า: "ถ้านามสกุลเหมือนกันให้จัดเรียงตามชื่อมิฉะนั้นให้เรียงตามนามสกุล"
แมตต์

4
ฉันเห็นรหัสสองสามกลิ่นที่นี่: 1) Contactอาจไม่ควรสืบทอดจากNSObject, 2) Contactน่าจะเป็นโครงสร้างและ 3) firstNameและlastNameไม่ควรเป็นตัวเลือกที่ไม่ได้ปิดกั้น
Alexander - คืนสถานะ Monica

3
@AMomchilov ไม่มีเหตุผลที่จะแนะนำให้ผู้ติดต่อควรเป็นโครงสร้างเพราะคุณไม่รู้ว่าโค้ดที่เหลือของเขาอาศัยความหมายอ้างอิงในการใช้อินสแตนซ์ของมันอยู่แล้วหรือไม่
Patrick Goley

3
@AMomchilov "น่าจะเป็น" ทำให้เข้าใจผิดเพราะคุณไม่รู้อะไรเลยเกี่ยวกับโค้ดเบสที่เหลือ หากเปลี่ยนเป็นโครงสร้างสำเนาทั้งหมดจะถูกสร้างขึ้นทันทีเมื่อทำการเปลี่ยนแปลง vars แทนที่จะแก้ไขอินสแตนซ์ในมือ นี่เป็นการเปลี่ยนแปลงพฤติกรรมอย่างรุนแรงและการทำเช่นนั้นอาจส่งผลให้เกิดข้อบกพร่องเนื่องจากไม่น่าเป็นไปได้ที่ทุกอย่างจะได้รับรหัสอย่างถูกต้องสำหรับทั้งความหมายอ้างอิงและค่าความหมาย
Patrick Goley

1
@AMomchilov ยังไม่ได้ยินเหตุผลว่าทำไมจึงควรเป็นโครงสร้าง ฉันไม่คิดว่า OP จะซาบซึ้งกับคำแนะนำที่ปรับเปลี่ยนความหมายของโปรแกรมที่เหลือโดยเฉพาะอย่างยิ่งเมื่อไม่จำเป็นต้องแก้ปัญหาในมือด้วยซ้ำ ไม่ทราบว่ากฎของคอมไพเลอร์เป็นกฎหมายสำหรับบางคน ... บางทีฉันอาจอยู่ในเว็บไซต์ที่ไม่ถูกต้อง
Patrick Goley

คำตอบ:


120

คิดว่า "การจัดเรียงตามเกณฑ์หลายเกณฑ์" หมายถึงอะไร หมายความว่ามีการเปรียบเทียบวัตถุสองชิ้นก่อนโดยใช้เกณฑ์เดียว จากนั้นหากเกณฑ์เหล่านั้นเหมือนกันความสัมพันธ์จะถูกทำลายโดยเกณฑ์ถัดไปและอื่น ๆ จนกว่าคุณจะได้รับคำสั่งที่ต้องการ

let sortedContacts = contacts.sort {
    if $0.lastName != $1.lastName { // first, compare by last names
        return $0.lastName < $1.lastName
    }
    /*  last names are the same, break ties by foo
    else if $0.foo != $1.foo {
        return $0.foo < $1.foo
    }
    ... repeat for all other fields in the sorting
    */
    else { // All other fields are tied, break ties by last name
        return $0.firstName < $1.firstName
    }
}

สิ่งที่คุณเห็นต่อไปนี้คือSequence.sorted(by:)วิธีการซึ่งพิจารณาการปิดที่ให้มาเพื่อพิจารณาว่าองค์ประกอบต่างๆเปรียบเทียบกันอย่างไร

หากการเรียงลำดับของคุณจะถูกนำมาใช้ในหลายสถานที่อาจจะดีกว่าที่จะทำให้ประเภทของคุณเป็นไปตามโปรโตคอลComparable วิธีการที่คุณสามารถใช้Sequence.sorted()วิธีการซึ่งให้คำปรึกษาการดำเนินงานของComparable.<(_:_:)ผู้ประกอบการเพื่อกำหนดวิธีการเปรียบเทียบองค์ประกอบ วิธีนี้คุณสามารถเรียงลำดับใด ๆSequenceของContacts โดยที่ไม่เคยมีการทำซ้ำรหัสการเรียงลำดับ


2
elseร่างกายต้องอยู่ระหว่าง{ ... }มิฉะนั้นรหัสไม่ได้รวบรวม
Luca Angeletti

เข้าใจแล้ว ฉันพยายามนำไปใช้ แต่ไม่สามารถทำให้ไวยากรณ์ถูกต้อง ขอบคุณมาก.
sbkl

สำหรับsortเทียบsortInPlaceดูที่นี่ ดูสิ่งนี้ด้านล่างมันเป็นแบบแยกส่วนมากขึ้น
ฮันนี่

sortInPlaceไม่สามารถใช้ได้ในสวิฟท์ 3 sort()แทนมันคุณจะต้องใช้ sort()จะกลายพันธุ์อาร์เรย์เอง นอกจากนี้ยังมีชื่อ func ใหม่sorted()ซึ่งจะส่งคืนอาร์เรย์ที่เรียงลำดับ
Honey

2
@AthanasiusOfAlex การใช้==ไม่ใช่ความคิดที่ดี ใช้ได้กับ 2 คุณสมบัติเท่านั้น ยิ่งไปกว่านั้นและคุณเริ่มทำซ้ำตัวเองด้วยนิพจน์บูลีนแบบผสมจำนวนมาก
Alexander - Reinstate Monica

123

การใช้ tuples เพื่อทำการเปรียบเทียบเกณฑ์ต่างๆ

วิธีง่ายๆในการจัดเรียงตามเกณฑ์หลายเกณฑ์ (เช่นการเรียงลำดับตามการเปรียบเทียบหนึ่งรายการและหากเทียบเท่าแล้วโดยการเปรียบเทียบอีกแบบหนึ่ง) คือการใช้tuplesเนื่องจากตัวดำเนินการ<และ>มีโอเวอร์โหลดสำหรับพวกเขาที่ทำการเปรียบเทียบพจนานุกรม

/// Returns a Boolean value indicating whether the first tuple is ordered
/// before the second in a lexicographical ordering.
///
/// Given two tuples `(a1, a2, ..., aN)` and `(b1, b2, ..., bN)`, the first
/// tuple is before the second tuple if and only if
/// `a1 < b1` or (`a1 == b1` and
/// `(a2, ..., aN) < (b2, ..., bN)`).
public func < <A : Comparable, B : Comparable>(lhs: (A, B), rhs: (A, B)) -> Bool

ตัวอย่างเช่น:

struct Contact {
  var firstName: String
  var lastName: String
}

var contacts = [
  Contact(firstName: "Leonard", lastName: "Charleson"),
  Contact(firstName: "Michael", lastName: "Webb"),
  Contact(firstName: "Charles", lastName: "Alexson"),
  Contact(firstName: "Michael", lastName: "Elexson"),
  Contact(firstName: "Alex", lastName: "Elexson"),
]

contacts.sort {
  ($0.lastName, $0.firstName) <
    ($1.lastName, $1.firstName)
}

print(contacts)

// [
//   Contact(firstName: "Charles", lastName: "Alexson"),
//   Contact(firstName: "Leonard", lastName: "Charleson"),
//   Contact(firstName: "Alex", lastName: "Elexson"),
//   Contact(firstName: "Michael", lastName: "Elexson"),
//   Contact(firstName: "Michael", lastName: "Webb")
// ]

สิ่งนี้จะเปรียบเทียบlastNameคุณสมบัติขององค์ประกอบก่อน ถ้าไม่เท่ากันลำดับการจัดเรียงจะขึ้นอยู่กับการ<เปรียบเทียบกับพวกเขา หากพวกเขามีความเท่าเทียมกันแล้วมันจะย้ายไปยังคู่ต่อไปขององค์ประกอบใน tuple คือการเปรียบเทียบfirstNameคุณสมบัติ

ไลบรารีมาตรฐานจัดเตรียม<และ>โอเวอร์โหลดสำหรับทูเปิล 2 ถึง 6 องค์ประกอบ

หากคุณต้องการจัดเรียงลำดับที่แตกต่างกันสำหรับคุณสมบัติที่แตกต่างกันคุณสามารถสลับองค์ประกอบในสิ่งต่อไปนี้:

contacts.sort {
  ($1.lastName, $0.firstName) <
    ($0.lastName, $1.firstName)
}

// [
//   Contact(firstName: "Michael", lastName: "Webb")
//   Contact(firstName: "Alex", lastName: "Elexson"),
//   Contact(firstName: "Michael", lastName: "Elexson"),
//   Contact(firstName: "Leonard", lastName: "Charleson"),
//   Contact(firstName: "Charles", lastName: "Alexson"),
// ]

ตอนนี้จะเรียงลำดับจากlastNameมากไปหาน้อยแล้วจากfirstNameน้อยไปมาก


การกำหนดsort(by:)โอเวอร์โหลดที่ใช้เพรดิเคตหลายตัว

แรงบันดาลใจจากการอภิปรายเกี่ยวกับการเรียงลำดับคอลเลกชันที่มีการmapปิดและ SortDescriptorsอีกทางเลือกหนึ่งคือการกำหนดโอเวอร์โหลดที่กำหนดเองsort(by:)และsorted(by:)เกี่ยวข้องกับเพรดิเคตหลายตัวซึ่งแต่ละเพรดิเคตจะได้รับการพิจารณาเพื่อตัดสินใจลำดับขององค์ประกอบ

extension MutableCollection where Self : RandomAccessCollection {
  mutating func sort(
    by firstPredicate: (Element, Element) -> Bool,
    _ secondPredicate: (Element, Element) -> Bool,
    _ otherPredicates: ((Element, Element) -> Bool)...
  ) {
    sort(by:) { lhs, rhs in
      if firstPredicate(lhs, rhs) { return true }
      if firstPredicate(rhs, lhs) { return false }
      if secondPredicate(lhs, rhs) { return true }
      if secondPredicate(rhs, lhs) { return false }
      for predicate in otherPredicates {
        if predicate(lhs, rhs) { return true }
        if predicate(rhs, lhs) { return false }
      }
      return false
    }
  }
}

extension Sequence {
  mutating func sorted(
    by firstPredicate: (Element, Element) -> Bool,
    _ secondPredicate: (Element, Element) -> Bool,
    _ otherPredicates: ((Element, Element) -> Bool)...
  ) -> [Element] {
    return sorted(by:) { lhs, rhs in
      if firstPredicate(lhs, rhs) { return true }
      if firstPredicate(rhs, lhs) { return false }
      if secondPredicate(lhs, rhs) { return true }
      if secondPredicate(rhs, lhs) { return false }
      for predicate in otherPredicates {
        if predicate(lhs, rhs) { return true }
        if predicate(rhs, lhs) { return false }
      }
      return false
    }
  }
}

( secondPredicate:พารามิเตอร์เป็นสิ่งที่ไม่ดี แต่จำเป็นเพื่อหลีกเลี่ยงการสร้างความคลุมเครือกับการsort(by:)โอเวอร์โหลดที่มีอยู่)

สิ่งนี้ช่วยให้เราสามารถพูด (โดยใช้contactsอาร์เรย์จากก่อนหน้านี้):

contacts.sort(by:
  { $0.lastName > $1.lastName },  // first sort by lastName descending
  { $0.firstName < $1.firstName } // ... then firstName ascending
  // ...
)

print(contacts)

// [
//   Contact(firstName: "Michael", lastName: "Webb")
//   Contact(firstName: "Alex", lastName: "Elexson"),
//   Contact(firstName: "Michael", lastName: "Elexson"),
//   Contact(firstName: "Leonard", lastName: "Charleson"),
//   Contact(firstName: "Charles", lastName: "Alexson"),
// ]

// or with sorted(by:)...
let sortedContacts = contacts.sorted(by:
  { $0.lastName > $1.lastName },  // first sort by lastName descending
  { $0.firstName < $1.firstName } // ... then firstName ascending
  // ...
)

แม้ว่าไซต์การโทรจะไม่รัดกุมเท่ากับตัวแปรทูเพิล แต่คุณจะได้รับความชัดเจนเพิ่มเติมจากสิ่งที่กำลังเปรียบเทียบและลำดับ


สอดคล้องกับ Comparable

หากคุณกำลังจะทำเหล่านี้ชนิดของการเปรียบเทียบอย่างสม่ำเสมอแล้วเป็น@AMomchilov & @appzYourLifeขอแนะนำให้คุณสามารถปฏิบัติตามContactเพื่อComparable:

extension Contact : Comparable {
  static func == (lhs: Contact, rhs: Contact) -> Bool {
    return (lhs.firstName, lhs.lastName) ==
             (rhs.firstName, rhs.lastName)
  }

  static func < (lhs: Contact, rhs: Contact) -> Bool {
    return (lhs.lastName, lhs.firstName) <
             (rhs.lastName, rhs.firstName)
  }
}

และตอนนี้เพียงแค่เรียกsort()ลำดับจากน้อยไปมาก:

contacts.sort()

หรือsort(by: >)ลำดับจากมากไปหาน้อย:

contacts.sort(by: >)

การกำหนดลำดับการจัดเรียงแบบกำหนดเองในประเภทที่ซ้อนกัน

หากคุณมีคำสั่งการจัดเรียงอื่น ๆ ที่คุณต้องการใช้คุณสามารถกำหนดได้ในประเภทซ้อน

extension Contact {
  enum Comparison {
    static let firstLastAscending: (Contact, Contact) -> Bool = {
      return ($0.firstName, $0.lastName) <
               ($1.firstName, $1.lastName)
    }
  }
}

แล้วเรียกง่ายๆว่า:

contacts.sort(by: Contact.Comparison.firstLastAscending)

contacts.sort { ($0.lastName, $0.firstName) < ($1.lastName, $1.firstName) } ช่วย ขอบคุณ
Prabhakar Kasi

ถ้าเช่นฉันคุณสมบัติที่จะถูกจัดเรียงเป็น optionals contacts.sort { ($0.lastName ?? "", $0.firstName ?? "") < ($1.lastName ?? "", $1.firstName ?? "") }แล้วคุณจะทำอะไรเช่นนี้
BobCowe

ฮอลลี่มอลลี่! เรียบง่าย แต่มีประสิทธิภาพ ... ทำไมฉันไม่เคยได้ยินมาก่อน! ขอบคุณมาก!
Ethenyl

@ BobCowe นั่นทำให้คุณรู้สึกว่า""เปรียบเทียบกับสตริงอื่น ๆ ได้อย่างไร (มาก่อนสตริงที่ไม่ว่างเปล่า) มันเป็นความหมายโดยนัยเป็นเวทมนตร์และไม่ยืดหยุ่นหากคุณต้องการให้nils มาที่ท้ายรายการแทน ฉันแนะนำให้คุณดูที่nilComparatorฟังก์ชันของฉันstackoverflow.com/a/44808567/3141234
Alexander - Reinstate Monica

19

อีกวิธีง่ายๆในการจัดเรียงด้วย 2 เกณฑ์ดังแสดงด้านล่าง

ตรวจสอบฟิลด์แรกในกรณีนี้คือlastNameถ้าไม่เท่ากันเรียงตามlastNameถ้าlastNameเท่ากันให้จัดเรียงตามฟิลด์ที่สองในกรณีfirstNameนี้

contacts.sort { $0.lastName == $1.lastName ? $0.firstName < $1.firstName : $0.lastName < $1.lastName  }

สิ่งนี้ให้ความยืดหยุ่นมากกว่าสิ่งทอ
Babac

5

สิ่งหนึ่งที่ประเภทศัพท์ไม่สามารถทำได้ตามที่ @ Hamish อธิบายไว้คือการจัดการทิศทางการเรียงลำดับที่แตกต่างกันพูดว่าเรียงลำดับตามฟิลด์แรกจากมากไปหาน้อยฟิลด์ถัดไปจากน้อยไปมาก ฯลฯ

ฉันสร้างบล็อกโพสต์เกี่ยวกับวิธีนี้ใน Swift 3 และทำให้โค้ดง่ายและอ่านได้

คุณสามารถค้นหาได้ที่นี่:

http://master-method.com/index.php/2016/11/23/sort-a-sequence-ie-arrays-of-objects-by-multiple-properties-in-swift-3/

คุณยังสามารถค้นหาที่เก็บ GitHub พร้อมรหัสได้ที่นี่:

https://github.com/jallauca/SortByMultipleFieldsSwift.playground

ความสำคัญของมันทั้งหมดเช่นหากคุณมีรายชื่อสถานที่คุณจะสามารถทำได้:

struct Location {
    var city: String
    var county: String
    var state: String
}

var locations: [Location] {
    return [
        Location(city: "Dania Beach", county: "Broward", state: "Florida"),
        Location(city: "Fort Lauderdale", county: "Broward", state: "Florida"),
        Location(city: "Hallandale Beach", county: "Broward", state: "Florida"),
        Location(city: "Delray Beach", county: "Palm Beach", state: "Florida"),
        Location(city: "West Palm Beach", county: "Palm Beach", state: "Florida"),
        Location(city: "Savannah", county: "Chatham", state: "Georgia"),
        Location(city: "Richmond Hill", county: "Bryan", state: "Georgia"),
        Location(city: "St. Marys", county: "Camden", state: "Georgia"),
        Location(city: "Kingsland", county: "Camden", state: "Georgia"),
    ]
}

let sortedLocations =
    locations
        .sorted(by:
            ComparisonResult.flip <<< Location.stateCompare,
            Location.countyCompare,
            Location.cityCompare
        )

1
"สิ่งหนึ่งที่ประเภทศัพท์ไม่สามารถทำได้ตามที่ @ Hamish อธิบายไว้คือการจัดการทิศทางการจัดเรียงที่แตกต่างกัน" - ใช่พวกเขาทำได้เพียงแค่สลับองค์ประกอบใน tuples;)
Hamish

ฉันพบว่านี่เป็นแบบฝึกหัดเชิงทฤษฎีที่น่าสนใจ แต่ซับซ้อนกว่าคำตอบของ @ Hamish มาก รหัสน้อยเป็นรหัสที่ดีกว่าในความคิดของฉัน
Manuel

5

คำถามนี้มีคำตอบที่ดีมากแล้ว แต่ผมอยากจะชี้ไปที่บทความ - เรียงอธิบายในสวิฟท์ เรามีหลายวิธีในการจัดเรียงเกณฑ์ต่างๆ

  1. การใช้ NSSortDescriptor วิธีนี้มีข้อ จำกัด บางอย่างวัตถุควรเป็นคลาสและสืบทอดมาจาก NSObject

    class Person: NSObject {
        var first: String
        var last: String
        var yearOfBirth: Int
        init(first: String, last: String, yearOfBirth: Int) {
            self.first = first
            self.last = last
            self.yearOfBirth = yearOfBirth
        }
    
        override var description: String {
            get {
                return "\(self.last) \(self.first) (\(self.yearOfBirth))"
            }
        }
    }
    
    let people = [
        Person(first: "Jo", last: "Smith", yearOfBirth: 1970),
        Person(first: "Joe", last: "Smith", yearOfBirth: 1970),
        Person(first: "Joe", last: "Smyth", yearOfBirth: 1970),
        Person(first: "Joanne", last: "smith", yearOfBirth: 1985),
        Person(first: "Joanne", last: "smith", yearOfBirth: 1970),
        Person(first: "Robert", last: "Jones", yearOfBirth: 1970),
    ]
    

    ตัวอย่างเช่นในที่นี้เราต้องการจัดเรียงตามนามสกุลจากนั้นชื่อสุดท้ายตามปีเกิด และเราต้องการใช้ตัวพิมพ์เล็กและใช้ภาษาของผู้ใช้

    let lastDescriptor = NSSortDescriptor(key: "last", ascending: true,
      selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
    let firstDescriptor = NSSortDescriptor(key: "first", ascending: true, 
      selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
    let yearDescriptor = NSSortDescriptor(key: "yearOfBirth", ascending: true)
    
    
    
    (people as NSArray).sortedArray(using: [lastDescriptor, firstDescriptor, yearDescriptor]) 
    // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1970), Joanne smith (1985), Joe Smith (1970), Joe Smyth (1970)]
    
  2. ใช้วิธีการเรียงลำดับอย่างรวดเร็วด้วยนามสกุล / ชื่อ วิธีนี้ควรใช้ได้กับทั้งคลาส / โครงสร้าง อย่างไรก็ตามเราไม่ได้จัดเรียงตาม yearOfBirth ที่นี่

    let sortedPeople = people.sorted { p0, p1 in
        let left =  [p0.last, p0.first]
        let right = [p1.last, p1.first]
    
        return left.lexicographicallyPrecedes(right) {
            $0.localizedCaseInsensitiveCompare($1) == .orderedAscending
        }
    }
    sortedPeople // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1985), Joanne smith (1970), Joe Smith (1970), Joe Smyth (1970)]
    
  3. วิธีที่รวดเร็วในการส่ง NSSortDescriptor สิ่งนี้ใช้แนวคิดที่ว่า 'functions are a first-class type' SortDescriptor เป็นประเภทฟังก์ชันรับค่าสองค่าส่งคืนบูล พูด sortByFirstName เราใช้สองพารามิเตอร์ ($ 0, $ 1) และเปรียบเทียบชื่อของพวกเขา ฟังก์ชันรวมจะใช้ SortDescriptors จำนวนมากเปรียบเทียบทั้งหมดและให้คำสั่ง

    typealias SortDescriptor<Value> = (Value, Value) -> Bool
    
    let sortByFirstName: SortDescriptor<Person> = {
        $0.first.localizedCaseInsensitiveCompare($1.first) == .orderedAscending
    }
    let sortByYear: SortDescriptor<Person> = { $0.yearOfBirth < $1.yearOfBirth }
    let sortByLastName: SortDescriptor<Person> = {
        $0.last.localizedCaseInsensitiveCompare($1.last) == .orderedAscending
    }
    
    func combine<Value>
        (sortDescriptors: [SortDescriptor<Value>]) -> SortDescriptor<Value> {
        return { lhs, rhs in
            for isOrderedBefore in sortDescriptors {
                if isOrderedBefore(lhs,rhs) { return true }
                if isOrderedBefore(rhs,lhs) { return false }
            }
            return false
        }
    }
    
    let combined: SortDescriptor<Person> = combine(
        sortDescriptors: [sortByLastName,sortByFirstName,sortByYear]
    )
    people.sorted(by: combined)
    // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1970), Joanne smith (1985), Joe Smith (1970), Joe Smyth (1970)]
    

    นี่เป็นสิ่งที่ดีเพราะคุณสามารถใช้ได้กับทั้งโครงสร้างและคลาสคุณยังสามารถขยายเพื่อเปรียบเทียบกับ nils ได้

อย่างไรก็ตามขอแนะนำให้อ่านบทความต้นฉบับ มีรายละเอียดมากขึ้นและอธิบายได้ดี


2

ฉันขอแนะนำให้ใช้โซลูชัน tuple ของ Hamishเนื่องจากไม่ต้องใช้รหัสเพิ่มเติม


หากคุณต้องการบางสิ่งที่ทำหน้าที่เหมือนifคำสั่งแต่ทำให้ตรรกะการแตกแขนงง่ายขึ้นคุณสามารถใช้วิธีนี้ซึ่งช่วยให้คุณทำสิ่งต่อไปนี้:

animals.sort {
  return comparisons(
    compare($0.family, $1.family, ascending: false),
    compare($0.name, $1.name))
}

นี่คือฟังก์ชั่นที่ให้คุณทำสิ่งนี้:

func compare<C: Comparable>(_ value1Closure: @autoclosure @escaping () -> C, _ value2Closure: @autoclosure @escaping () -> C, ascending: Bool = true) -> () -> ComparisonResult {
  return {
    let value1 = value1Closure()
    let value2 = value2Closure()
    if value1 == value2 {
      return .orderedSame
    } else if ascending {
      return value1 < value2 ? .orderedAscending : .orderedDescending
    } else {
      return value1 > value2 ? .orderedAscending : .orderedDescending
    }
  }
}

func comparisons(_ comparisons: (() -> ComparisonResult)...) -> Bool {
  for comparison in comparisons {
    switch comparison() {
    case .orderedSame:
      continue // go on to the next property
    case .orderedAscending:
      return true
    case .orderedDescending:
      return false
    }
  }
  return false // all of them were equal
}

หากคุณต้องการทดสอบคุณสามารถใช้รหัสพิเศษนี้:

enum Family: Int, Comparable {
  case bird
  case cat
  case dog

  var short: String {
    switch self {
    case .bird: return "B"
    case .cat: return "C"
    case .dog: return "D"
    }
  }

  public static func <(lhs: Family, rhs: Family) -> Bool {
    return lhs.rawValue < rhs.rawValue
  }
}

struct Animal: CustomDebugStringConvertible {
  let name: String
  let family: Family

  public var debugDescription: String {
    return "\(name) (\(family.short))"
  }
}

let animals = [
  Animal(name: "Leopard", family: .cat),
  Animal(name: "Wolf", family: .dog),
  Animal(name: "Tiger", family: .cat),
  Animal(name: "Eagle", family: .bird),
  Animal(name: "Cheetah", family: .cat),
  Animal(name: "Hawk", family: .bird),
  Animal(name: "Puma", family: .cat),
  Animal(name: "Dalmatian", family: .dog),
  Animal(name: "Lion", family: .cat),
]

ความแตกต่างหลักจากโซลูชันของ Jamieคือการเข้าถึงคุณสมบัติถูกกำหนดแบบอินไลน์แทนที่จะเป็นวิธีการแบบคงที่ / อินสแตนซ์ในคลาส เช่น$0.familyแทนที่จะเป็นAnimal.familyCompare. และการขึ้น / ลงจะถูกควบคุมโดยพารามิเตอร์แทนที่จะเป็นตัวดำเนินการที่โอเวอร์โหลด โซลูชันของ Jamie เพิ่มส่วนขยายบน Array ในขณะที่โซลูชันของฉันใช้ built in sort/ sortedmethod แต่ต้องกำหนดเพิ่มเติมอีกสองรายการ: compareและcomparisons.

เพื่อประโยชน์ครบถ้วนนี่เป็นวิธีการแก้ปัญหาของฉันเปรียบเทียบกับวิธีการแก้ปัญหาของ tuple Hamish เพื่อแสดงให้เห็นว่าฉันจะใช้ตัวอย่างป่าที่เราต้องการจัดเรียงผู้คนโดย(name, address, profileViews)วิธีการแก้ปัญหาของ Hamish จะประเมินค่าคุณสมบัติทั้ง 6 อย่างหนึ่งครั้งก่อนที่การเปรียบเทียบจะเริ่มขึ้น สิ่งนี้อาจไม่ต้องการหรือไม่ก็ได้ ตัวอย่างเช่นสมมติว่าprofileViewsเป็นการโทรผ่านเครือข่ายที่มีราคาแพงเราอาจต้องการหลีกเลี่ยงการโทรprofileViewsเว้นแต่ว่าจำเป็นจริงๆ วิธีการแก้ปัญหาของฉันจะหลีกเลี่ยงการประเมินprofileViewsจนกว่าและ$0.name == $1.name $0.address == $1.addressอย่างไรก็ตามเมื่อมีการประเมินprofileViewsก็มีแนวโน้มที่จะประเมินหลายครั้งมากกว่าหนึ่งครั้ง


1

เกี่ยวกับ:

contacts.sort() { [$0.last, $0.first].lexicographicalCompare([$1.last, $1.first]) }

lexicographicallyPrecedesกำหนดให้ทุกประเภทในอาร์เรย์เหมือนกัน ตัวอย่างเช่น[String, String]. อะไร OP อาจต้องการคือการผสมและประเภทการทำงาน: เพื่อให้พวกเขาสามารถทำอะไรได้[String, Int, Bool] [$0.first, $0.age, $0.isActive]
Senseful

-1

ที่ใช้ได้กับอาร์เรย์ [String] ของฉันใน Swift 3 และดูเหมือนว่าใน Swift 4 ก็โอเค

array = array.sorted{$0.compare($1, options: .numeric) == .orderedAscending}

คุณอ่านคำถามก่อนตอบหรือไม่? จัดเรียงตามพารามิเตอร์หลายตัวไม่ใช่สิ่งเดียวที่คุณนำเสนอ
Vive
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.