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
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]
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 struct
s:
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) }
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.
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.