Sort array Name Field that has first an last name by Last Name?

I have a field that contains first name as well as last name in an array. I want to sort on last name and then on first name. Not sure how this would be coded.

How is this done?

Thanks

If you are storing your data in a struct, make it conform to Comparable:

struct Name : Comparable {
   var firstName: String
   var lastName: String
}

then, define at least the following functions:

func == (_ lname: Name, _ rname: Name) -> Bool {
   return lname.lastName == rname.lastName  && lname.firstName == rname.firstName) 
}

func <  (_ lname: Name, _ rname: Name) -> Bool {
   return lname.lastName < rname.lastName || (lname.lastName == rname.lastName 
                                                  && lname.firstName < rname.firstName) 
}

You could also write a > fun if you want descending order along similar lines, or:

func > (_ lname: Name, _ rname: Name) -> Bool {
   return rname < lname;
}

Then, use the sort() function on your Array[Name]

1 Like

What you have makes sense, but I get an error when I change:

struct Customers {
    var id: Int64?
    var CustNumber: String?
    var CustName: String? 

To This

    struct Customers {
        var id: Int64?
        var CustNumber: String?
        var CustName: Comparable {
              var firstName: String
              var lastName: String
    }

This is the error I get:

Protocol 'Comparable' can only be used as a generic constraint because it has Self or associated type requirements

You need to define a struct that is comparable. Now you declared the var to be comparable, which you cannot do AFAIK.

Maybe you should read the Swift programming language guide first?

Comparable is a protocol, not a structure. If you wanted to have a variable which simply conforms to Comparable, you would need generics:

struct Customer<T: Comparable> {
    var id: Int
    var number: String
    var name: T
}

...but I don't think this is the desired behavior. Like andreas66 said above,

The whole solution is to take Jonathan's Name structure and use that as the variable type:

struct Customer {
    var id: Int
    var number: String
    var name: Name
}

I read Paul Hudson section on protocols and what I read make sense. So, I decided to put the code in a playground to learn more about this.

This is what I have.

var counter = 0
counter += 1
print(counter)
var custData = Customers(CustNumber: "1", CustName: "Bill Jones")
struct Customers {
 var CustNumber: String?
 var CustName:  String?
}
struct CustName : Comparable {
   var firstName: String
   var lastName: String
}
func <  (_ lname: CustName, _ rname: CustName) -> Bool {
   return lname.lastName < rname.lastName || (lname.lastName == rname.lastName
                                                  && lname.firstName < rname.firstName)
}
func == (_ lname: CustName, _ rname: CustName) -> Bool {
   return lname.lastName == rname.lastName  && (lname.firstName == rname.firstName)
}
// Populate Customers
let allCustData = [
    Customers(CustNumber: "1", CustName: "Andy Smith"),
    Customers(CustNumber: "2", CustName: "Tebo Chalas"),
    Customers(CustNumber: "3", CustName: "Charles Ariba"),
    Customers(CustNumber: "4", CustName: "Donald Tabb"),
    Customers(CustNumber: "5", CustName: "Tim Word"),
    Customers(CustNumber: "6", CustName: "Bill Lee"),
]

I am not sure if this is on the right track as well as how to sort and then print the sorted results. I tried several things but did not get the desired results.

Thanks again for the help.

Here a revised version of what I think you want

struct CustName : Comparable {
	var firstName: String = String()
	var lastName: String = String()

	init(firstName first: String, lastName last: String)
	{
		self.firstName = first
		self.lastName = last
	}

	init?(_ name: String?) {
		guard let str = name else { return nil; }
		let firstSpace = str.firstIndex(of: " ") ?? str.endIndex
		let first = String(str[..<firstSpace])
		let lastSpace = str.lastIndex(of: " ") ?? str.endIndex
		let last = String(str[lastSpace..<str.endIndex])

		self.init(firstName: first, lastName: last)
	}
}

func <  (_ lname: CustName, _ rname: CustName) -> Bool {
	return lname.lastName < rname.lastName || (lname.lastName == rname.lastName
		&& lname.firstName < rname.firstName)
}
func == (_ lname: CustName, _ rname: CustName) -> Bool {
	return lname.lastName == rname.lastName  && (lname.firstName == rname.firstName)
}

struct Customer {
	var custNumber: String? = nil
	var custName:  CustName? = nil

	init(custNumber number: String, custName name: CustName)
	{
		self.custNumber = number;
		self.custName = name;
	}
}

// Populate Customers
let Customers : [Customer] = [
	Customer(custNumber: "1", custName: CustName("Bill Jones")!),
	Customer(custNumber: "2", custName: CustName(firstName: "Andy", lastName: "Smith")),
	Customer(custNumber: "3", custName: CustName(firstName: "Tebo", lastName: "Chalas")),
	Customer(custNumber: "4", custName: CustName(firstName: "Charles", lastName: "Ariba")),
	Customer(custNumber: "5", custName: CustName(firstName: "Donald", lastName: "Tabb")),
	Customer(custNumber: "6", custName: CustName(firstName: "Tim", lastName: "Word")),
	Customer(custNumber: "7", custName: CustName(firstName: "Bill", lastName: "Lee")),
]

You need to make the CustName the member of your Customer struct, not a string. You need to create a CustName instance from a String, which is what the init? initializer is doing.

Also, you should distinguish between variables and types. The Swift convention seems to be to uppercase the first letter of a type (CustName), and a variable (custName).

Also, with naming structs:

Place Name inside Customer:

struct Customer {
    struct Name: Comparable {
        var firstName: String
        var lastName: String
        
        init(firstName first: String, lastName last: String)
        {
            self.firstName = first
            self.lastName = last
        }
        
        init?(_ name: String?) {
            guard let str = name else { return nil; }
            let firstSpace = str.firstIndex(of: " ") ?? str.endIndex
            let first = String(str[..<firstSpace])
            let lastSpace = str.lastIndex(of: " ") ?? str.endIndex
            let last = String(str[lastSpace..<str.endIndex])
            
            self.init(firstName: first, lastName: last)
        }
        
        static func <  (_ lname: Name, _ rname: Name) -> Bool {
            return lname.lastName < rname.lastName || (lname.lastName == rname.lastName
                && lname.firstName < rname.firstName)
        }
        static func == (_ lname: Name, _ rname: Name) -> Bool {
            return lname.lastName == rname.lastName  && (lname.firstName == rname.firstName)
        }
    }
    
    
    var number: String
    var name:Name
    
    init(custNumber number: String, custName name: Name) {
        self.number = number;
        self.name = name;
    }
}

// Populate Customers
let Customers : [Customer] = [
    Customer(custNumber: "1", custName: Customer.Name("Bill Jones")!),
    Customer(custNumber: "2", custName: Customer.Name(firstName: "Andy", lastName: "Smith")),
    Customer(custNumber: "3", custName: Customer.Name(firstName: "Tebo", lastName: "Chalas")),
    Customer(custNumber: "4", custName: Customer.Name(firstName: "Charles", lastName: "Ariba")),
    Customer(custNumber: "5", custName: Customer.Name(firstName: "Donald", lastName: "Tabb")),
    Customer(custNumber: "6", custName: Customer.Name(firstName: "Tim", lastName: "Word")),
    Customer(custNumber: "7", custName: Customer.Name(firstName: "Bill", lastName: "Lee")),
]

If you don't want to canonicalise this ordering into a Comparable conformance you could as well just pass a comparator parameter to the sort or sorted function. The implementation can be simplified by using the fact that tuples already sort lexicographically:

people.sort { ($0.lastName, $0.firstName)
            < ($1.lastName, $1.firstName) }
2 Likes

Thanks for all the replies. They have been very helpful, I come from a IBM C, COBOL, Assembler, REXX etc. coding background so the transition to swift had been daunting.

At this point I have added the coding you all supplied to my project but I am not sure how to connected it or use it within the framework of my project.

Let me supply some more details.

  • I am using SQLite for a backend
  • I am using a structure called Customers to reference the rows in the SQLite table
  • I am using a tableview in Interface Builder to display customer data.
  • I want to sort on a column that contains customer name (First Name and Last Name)

So, in ViewController code I have the following:

func setSortDescriptor() {
        let descriptorID = NSSortDescriptor(key: "custnumber", ascending: true)
        tableView.tableColumns[0].sortDescriptorPrototype = descriptorID
        let descriptorName = NSSortDescriptor(key: "custname", ascending: true)
        tableView.tableColumns[1].sortDescriptorPrototype = descriptorName
        let descriptorDate = NSSortDescriptor(key: "custdate", ascending: true)
        tableView.tableColumns[8].sortDescriptorPrototype = descriptorDate
    }

and in the DataSource of my View controller I have the following:

  func tableView(_ tableView: NSTableView, sortDescriptorsDidChange     oldDescriptors: [NSSortDescriptor]) {
            guard let sortDescriptor = tableView.sortDescriptors.first else { return }
            let custColumn = sortDescriptor.key!
            custviewModel.sortCustomers(custColumn: custColumn, ascending: sortDescriptor.ascending)
            tableView.reloadData()
        }

and in custviewModel I have the following:

class CustViewModel: NSObject {

// MARK: - Properties

var customers = [Customers]()

    // MARK: - Init

override init() {
    super.init()
    customers = getAllCustomers()
}



// MARK: - Private Methods


// MARK: - Public Methods



func sortCustomers(custColumn: String, ascending: Bool) {
     switch custColumn {
    case "custname":
        customers.sort { (p1, p2) -> Bool in
            guard let id1 = p1.id, let id2 = p2.id else { return true }
            if ascending {
                return id1 < id2
            } else {
                return id2 < id1
            }
        }

As you can see the sort for customer name currently sorts on the entire Name Column and the customer variable contains my SQLIte rows that I now sort on and reload the tableview.

So how do use the code you all have supplied? I think it needs to be in the sortCustomers function?

Thanks

I would suggest the following:

1 - If your Customers type is not what I changed to Customer in my example, then, you should make your customers array an array of Customer ([Customer]). By the way, you don't need the initializer call since you are initializing the array in your initializer. You can get away with var customers : [Customer] and you don't execute any code since you are going to actually initialize later.

2 - Not sure what getAllCustomers does, but, I'm assuming you are fetching rows from your database and initializing Customer instances. If you are using the Customer type, when you create an instance with two strings, you'll get an instance of Customer with a custNumber String, and a custName CustName or Customer.Name depending on whose example you use. Key point is the the custName property has the < and == operators defined on it that you can now use in your sort call. By the way, you should probably figure out what to do with if you get two names that are equal.

Jonathan,

Suggestion 1 -- That was my first thought as well but I could not figure out how to code the remaining content in the Customer array as I have in the Customers array.

Here is my Customers Model:
struct Customers {
var id: Int64?
var custNumber: String?
var custName: String?
var custAddr: String?
var custCity: String?
var custState: String?
var custZip: String?
var custMobile: String?
var custEmail: String?
var custNotes: String?
var custAdded: Date?
}

extension Customers: Codable, FetchableRecord, MutablePersistableRecord {
    
    mutating func didInsert(with rowID: Int64, for column: String?) {
        id = rowID
    }
}
extension Customers {
    
    public enum Columns {
        static let id = Column(CodingKeys.id)
        static let custNumber = Column(CodingKeys.custNumber)
        static let custName = Column(CodingKeys.custName)
        static let custAddr = Column(CodingKeys.custAddr)
        static let custCity = Column(CodingKeys.custCity)
        static let custState = Column(CodingKeys.custState)
        static let custZip = Column(CodingKeys.custZip)
        static let custMobile = Column(CodingKeys.custMobile)
        static let custEmail = Column(CodingKeys.custEmail)
        static let custNotes = Column(CodingKeys.custNotes)
        static let custAdded = Column(CodingKeys.custAdded)
    }

So how do I get Customer model to have custAddr, custCity etc..?

Yes getAllCustomers fetches all customer rows.

Thanks for all your help

Got this working the way I wanted. My issue was old paradigm
from previous languages. Now I understand more the swift language and what is means to be a type safe language as well as structs and nested structs.

Thanks for the help.

1 Like