Sort Array of Objects With Some Conditions - arrays

I have an array of Users objects:
Class Users {
var firstName:String!
var lastName:String!
var status:Int!
override init(fn: String, ln: String, s: Int) {
self.firstName = fn
self.lastName = ln
self.status = s
}
}
var users:[Users] = [Users]()
users.append(Users(fn:"a", ln:"b", s:1))
users.append(Users(fn:"c", ln:"d", s:3))
users.append(Users(fn:"e", ln:"f", s:2))
users.append(Users(fn:"g", ln:"h", s:1))
users.append(Users(fn:"i", ln:"j", s:1))
users.append(Users(fn:"k", ln:"l", s:2))
I know the method to sort this array on status like
users = users.sorted(by: {$0.status > $1.status})
But, how could I sort users array on status with 2 on top then 3 and in the last 1 i.e [2,2,3,1,1,1]

Consider refactoring:
Class to struct:
struct User {
var firstName:String!
var lastName:String!
var status:Status
}
Status enum:
enum Status: Int {
case online, busy, offline
}
Population:
var users: [User] = [
User(firstName: "a", lastName: "b", status: .online),
User(firstName: "c", lastName: "d", status: .offline),
User(firstName: "e", lastName: "f", status: .busy),
User(firstName: "g", lastName: "h", status: .online),
User(firstName: "i", lastName: "j", status: .online),
User(firstName: "k", lastName: "l", status: .busy)
]
Sort:
users = users.sorted(by:{$0.status.rawValue < $1.status.rawValue })
users.forEach { print($0) }
Output:
User(firstName: a, lastName: b, status: .online)
User(firstName: g, lastName: h, status: .online)
User(firstName: i, lastName: j, status: .online)
User(firstName: e, lastName: f, status: .busy)
User(firstName: k, lastName: l, status: .busy)
User(firstName: c, lastName: d, status: .offline)

Try this. This will sort based on the order array. If any status is missing will be moved to bottom of the array.
let order = [2,3,1]
users = users.sorted(by: { order.index(of: $0.status) ?? Int.max < order.index(of: $1.status) ?? Int.max })
// 2,2,3,1,1,1
Lets say if 3 is missing in order array
let order = [2,1]
users = users.sorted(by: { order.index(of: $0.status) ?? Int.max < order.index(of: $1.status) ?? Int.max })
// 2,2,1,1,1,3

Use a lookup for your sort order.
let sortPriority: [Int: Int] = [ 1: 3, 2: 1, 3: 2]
Also you can use the mutating sort to sort the array in place instead of sorted which returns a copy since it looks like you are discarding the original array:
users.sort(by: {sortPriority [$0.status, default: Int.max] < sortPriority [$1.status, default: Int.max]})
print(users.flatMap{$0.status})
output:
[2, 2, 3, 1, 1, 1]

Related

How do you sort an array of structs in Swift 5.x (macOS app) [duplicate]

I've got the below struct and would like to sort the items within sessions by startTime field. I'm completely lost on how to do this.
I tried:
let sortedArray = sessionsData?.items.sorted{ ($0["startTime"] as! String) < ($1["startTime"] as! String) }
but that just gives me an error about no subscript members?
Any pointers would really be appreciated, thank you.
public struct sessions: Decodable {
let status: String?
let start: Int?
let count: Int?
let items: [sessionInfo]?
let itemsCount: Int?
let multipart: Bool?
let startTime: Int?
let endTime: Int?
}
public struct sessionInfo: Decodable {
let name: String?
let datalist: String?
let sessionType: Int?
let status: Int?
let backupType: Int?
let startTime: Int?
let endTime: Int?
let owner: String?
let numOfErrors: Int?
let numOfWarnings: Int?
let flags: Int?
}
I tried the below, but get an error:
var sortedArray = sessionsData?.items?.sorted(by: { (lhs, rhs) -> Bool in
return lhs.startTime < rhs.startTime
})
error:
Binary operator '<' cannot be applied to two 'Int?' operands
Try below code, it sorts the struct in asc order but it pushes nil timestamps to bottom.
if you want nil timestamps to be at top, make all nil checks in below code to return opposite of what i return in below code.
var sortedArray = sessionsData?.items?.sorted(by: { (lhs, rhs) -> Bool in
if let lhsTime = lhs.startTime, let rhsTime = rhs.startTime {
return lhs.startTime < rhs.startTime
}
if lhs.startTime == nil && rhs.startTime == nil {
// return true to stay at top
return false
}
if lhs.startTime == nil {
// return true to stay at top
return false
}
if rhs.startTime == nil {
// return false to stay at top
return true
}
})
You should access the fields directly and not through subscripts.
let sortedArray = sessionsData?.items.sorted(by: {$0.startTime < $1.startTime})
You could write this (tested in playground) :
var sortedArray = sessionsData?.items?.sorted(by: { (lhs, rhs) -> Bool in return (lhs.startTime ?? 0) < (rhs.startTime ?? 0) })
If one is optional, you do not crash, even though result of comparison is meaningless
You have default High Order Function for sorting the struct array in ascending and descending order
Example
let roster: [TeamMember] = [.init(id: 1, name: "Abishek", age: 19),
.init(id: 2, name: "Dinesh", age: 22),
.init(id: 3, name: "Praveen", age: 24),
.init(id: 4, name: "Sam", age: 25),
.init(id: 5, name: "David", age: 21)]
let descendingSorted = roster.sorted{$0.name > $1.name} // for descending order
let ascendingSorted = roster.sorted{$0.name < $1.name} // for ascending order
print(descendingSorted)
print(ascendingSorted)
Your Output
// for descending order
[TeamMember(id: 4, name: "Sam", age: 25.0),
TeamMember(id: 3, name: "Praveen", age: 24.0),
TeamMember(id: 2, name: "Dinesh", age: 22.0),
TeamMember(id: 5, name: "David", age: 21.0),
TeamMember(id: 1, name: "Abishek", age: 19.0)]
// for ascending order
[TeamMember(id: 1, name: "Abishek", age: 19.0),
TeamMember(id: 5, name: "David", age: 21.0),
TeamMember(id: 2, name: "Dinesh", age: 22.0),
TeamMember(id: 3, name: "Praveen", age: 24.0),
TeamMember(id: 4, name: "Sam", age: 25.0)]
And we have another one method for sorting is SortComparator. we sort the result based on ComparisonResult
let descendingSorted1 = roster.sorted { teamMember1, teamMember2 in
return teamMember1.name.compare(teamMember2.name) == .orderedDescending
} // for descending order
let ascendingSorted1 = roster.sorted{ teamMember1, teamMember2 in
return teamMember1.name.compare(teamMember2.name) == .orderedAscending
} // for ascending order
print(descendingSorted1)
print(ascendingSorted1)

How to deal out 2 different length arrays by the value?

Think of a company, employees work in different capacities. Employees will be employed according to their capacities. There should be similar capacities for every day.
I want the array1's values ​​in the days array to be equally distributed.
For example: Thursday -> 2 item and 5 value, Friday -> 1 item and 5 value
Saturday -> 1 item and 6 value, Sunday -> 2 item and 6 value.
Let it be distributed with similar numbers.
I have 2 array like this:
let days = ["Thursday","Friday","Saturday","Sunday"]
var array1: [T] = [T(name: "a", value: 3),T(name: "b", value: 2),T(name: "c", value: 5),T(name: "d", value: 1),T(name: "e", value: 6),T(name: "f", value: 4)]
How can I deal out array1 objects to days array?
I tried this:
var arrayValueTotal = 0
for t in array1{
arrayValueTotal = arrayValueTotal + t.value
}
var upperLimit = Double(Double(arrayValueTotal)/Double(days.count)) // This will get value per day. 22/4 = 5.5
for i in (0...days.count-1){ //Handling the days
for j in array1{
if j.value >= Int(upperLimit){ //handling the array1's value
}else if j.value < Int(upperLimit){
}
}
}
Desired result like:
let combinedResult = [("Thursday": [T(name: "a", value: 3),
T(name: "b", value: 2)]),
("Friday": [T(name: "c", value: 5)]),
("Saturday": [T(name: "e", value: 6)]),
("Sunday": [T(name: "f", value: 5),
T(name: "d", value: 1)])]
Here is the Class T:
class T {
var name:String
var value:Int
init(name:String, value:Int) {
self.name = name
self.value = value
}
}
How can I get that? Thanks in advance.
Ok, if I understood correctly, let's start by separating array1 according to limit (that you calculated yourself):
var array1: [T] = [T(name: "a", value: 3),
T(name: "b", value: 2),
T(name: "c", value: 5),
T(name: "d", value: 1),
T(name: "e", value: 6),
T(name: "f", value: 4)]
let reducedArray = array1.reduce(into: [[T]]()) { result, current in
guard var last = result.last else { result.append([current]); return }
if last.reduce(0, { $0 + $1.value }) < limit {
last.append(current)
result[result.count - 1] = last
} else {
result.append([current])
}
}
Then reducedArray is:
[
[T(name:a, value: 3), T(name:b, value: 2)],
[T(name:c, value: 5)],
[T(name:d, value: 1), T(name:e, value: 6)],
[T(name:f, value: 4)]
]
Then
var final: [(String, [T])] = []
for i in 0..<min(days.count, reducedArray.count) { //If the count aren't equal, what to do with the left values?
final.append((days[i], reducedArray[i]))
}
print("Final: \(final)")
With final being:
[
("Thursday", [T(name:a, value: 3), T(name:b, value: 2)]),
("Friday", [T(name:c, value: 5)]),
("Saturday", [T(name:d, value: 1), T(name:e, value: 6)]),
("Sunday", [T(name:f, value: 4)])
]

Fetch data from an array with elements of second array

I have one array "users" with all user data and and second array "userIds" having user's id. I have to fetch User from "users" array using "userIds" array
struct User {
let name: String
let id: Int
}
let users: [User] = [User(name: "a", id: 1),
User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "d", id: 4),
User(name: "d", id: 5)]
let userIds = [2,3,2,5]
result array that I want is :
[User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "b", id: 2),
User(name: "d", id: 5)]
so it can have duplicate data according to the data in "userIds".
Now I tried using Higher order function filter:
let result = users.filter { (user) -> Bool in
return userIds.contains(user.id)
}
but this removes the duplicate data and the output is :
[User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "d", id: 5)]
One approach that I tried is using for loop :
var result = [User]()
for i in userIds {
result.append(users.filter({ $0.id == i }).first!)
}
which gives the desired output but if there is a better approach please suggest.
You can solve this using first(where:) to search through users:
let result = userIds.compactMap { desiredDataValue in
users.first(where: { $0.id == desiredDataValue })
}
But if you're doing this a lot, it would probably speed things up if you built a datastructure that allows for fast lookup by the "id" value. You should compare the performance for yourself, and see if you do this enough/frequently enough for it to be worthwhile:
let dictsByData = Dictionary(uniqueKeysWithValues:
users
.lazy
.map { dict in
(key: dict.id, value: dict)
}
)
let result = userIds.compactMap { desiredDataValue in dictsByData[desiredDataValue]! }
result.forEach { print($0) }
Well after digging few more and with the help of this blog:
https://medium.com/#abhimuralidharan/higher-order-functions-in-swift-filter-map-reduce-flatmap-1837646a63e8
I tried doing like this:
let results = userIds.compactMap { (int) -> User? in
var matchedUser: User?
if users.contains(where: { (user) -> Bool in
if user.id == int {
matchedUser = user
}
return user.id == int
}) {
return matchedUser
}
return nil
}
and in playground I checked the count the code was executed :
and it seems like the count is less comparing to "for" loop.

Swift - Grouping elements according to their equal features

In Swift 4, I have:
let customers = [Customer(name: "John", country: "US", profession: "Engineer"), Customer(name: "Mary", country: "UK", profession: "Nurse"), Customer(name: "Diana", country: "US", profession: "Engineer"), Customer(name: "Paul", country: "US", profession: "Plumber"), Customer(name: "Sam", country: "UK", profession: "Nurse")]
I would like to have for example a function that could filter the elements in customers, so that each time the names and professions of at least 2 elements in it are equal, they are added to an array automatically created by this function :
var customers1 = [Customer(name: "John", country: "US", profession: "Engineer"), Customer(name: "Diana", country: "US", profession: "Engineer")]
var customers2 = [Customer(name: "Mary", country: "UK", profession: "Nurse"), Customer(name: "Sam", country: "UK", profession: "Nurse")]
I searched without success, however I picked some solutions that perhaps could be adapted to this case:
extension Array where Element: Comparable {
func containsSameElements(as other: [Element]) -> Bool {
return self[1] == other[1] && self[2] == other[2]
}
}
or
func ==<Element : Equatable> (lhs: [[Element]], rhs: [[Element]]) -> Bool {
return lhs.elementsEqual(rhs, by: ==)
}
or
elementsEqual()/contains() with a loop.
or
a combination of flatMap()/reduce()/filter().
Thank you.
Based on your feedback and clarification, I would do something like this.
struct CountryAndProfession: Hashable, CustomStringConvertible {
let country: String
let profession: String
var description: String {
return "CountryAndProfession{country: \(country), profession: \(profession)}"
}
var hashValue: Int {
return "\(country)__\(profession)".hashValue
}
static func ==(left: CountryAndProfession, right: CountryAndProfession) -> Bool {
return left.country == right.country && left.profession == right.profession
}
}
// The Customer Type you apparently are dealing with. (May need a custom init depending on your use case
struct Customer: CustomStringConvertible {
let name: String
let countryAndProfession: CountryAndProfession
var description: String {
return "Customer{name: \(name), countryAndProfession: \(countryAndProfession)}"
}
// returns a dictionary with the passed customer's CountryAndProfession as the key, and the matching
static func makeDictionaryWithCountryAndProfession(from customers: [Customer]) -> [CountryAndProfession : [Customer]] {
var customersArrayDictionary: [CountryAndProfession : [Customer]] = [:]
customers.forEach { (customer) in
if customersArrayDictionary.keys.contains(customer.countryAndProfession) {
customersArrayDictionary[customer.countryAndProfession]?.append(customer)
}
else {
customersArrayDictionary[customer.countryAndProfession] = [customer]
}
}
return customersArrayDictionary
}
static func getArraysBasedOnCountries(from customerArray: [Customer]) -> [[Customer]] {
return Array(makeDictionaryWithCountryAndProfession(from: customerArray).values)
}
}
let arrayOfArrays = [["John", "A", "A" ], ["Mary", "A", "B" ], ["Diana", "A", "A" ], ["Paul", "B", "B" ], ["Sam", "A", "B" ]]
//If you're dealing with non-predictable data, you should probably have some Optionality
let allCustomers = arrayOfArrays.map{ Customer(name: $0[0], countryAndProfession: CountryAndProfession(country: $0[1], profession: $0[2])) }
let splitCustomers = Customer.getArraysBasedOnCountries(from: allCustomers)
//contains [[John, Diana], [Mary, Sam], [Paul]]
I'm still not quite sure what you want your final result to look like (something that is always helpful to put in the question), but you should be able to get the result you're looking for using makeDictionaryWithCountryAndProfession combined with the specific CountryAndProfession you're looking for or using .filter
This is how I would recommend doing what you're trying to do:
struct Customer {
let name: String
let country: String
let profession: String
func countryMatches(with otherCustomer: Customer) -> Bool {
return country == otherCustomer.country
}
func professionMatches(with otherCustomer: Customer) -> Bool {
return profession == otherCustomer.profession
}
func countryAndProfessionMatch(with otherCustomer: Customer) -> Bool {
return countryMatches(with: otherCustomer) && professionMatches(with: otherCustomer)
}
static func getAllCustomersWithProfessionsAndCountriesMatching(with customer: Customer, from allCustomers: [Customer]) -> [Customer] {
return allCustomers.filter { customer.countryAndProfessionMatch(with: $0) }
}
}
let arrayOfArrays = [["John", "A", "A" ], ["Mary", "A", "B" ], ["Diana", "A", "A" ], ["Paul", "B", "B" ], ["Sam", "A", "B" ]]
//If you're dealing with non-predictable data, you should probably have some Optionality
let allCustomers = arrayOfArrays.map{ Customer(name: $0[0], country: $0[1], profession: $0[2]) }
let allCustomersMatchingJohnProperties = Customer.getAllCustomersWithProfessionsAndCountriesMatching(with: allCustomers[0], from: allCustomers)
// contains John and Diane
let allCustomersMatchingMaryProperties = Customer.getAllCustomersWithProfessionsAndCountriesMatching(with: allCustomers[1], from: allCustomers)
// contains Mary and Sam
I believe this does what you're looking to do, but with a more structured/maintainable approach.
getAllCustomersWithProfessionsAndCountriesMatching is almost definitely way too long, but left it that way to be clear for the answer. I would advice renaming it to fit your use case.

Finding value in array containing custom class

I'm trying to find the place of a value in a an array containing structures.
My array looks like this
struct User {
var firstName: String?
var lastName: String?
}
var allThePeople = [User(firstName: "John", lastName: "Doe"), User(firstName: "Jane", lastName: "Doe"), User(firstName: "John", lastName: "Travolta")];
Is there a way to get the places for all "Doe"'s in the array? (in this case 0 and 1)
You can filter allThePeople with a condition to get all the people with the last name "Doe".
let allTheDoes = allThePeople.filter { $0.lastName == "Doe" }
You can enumerate the array and flat map it to an array of indices.
let allTheDoeIndexes = allThePeople.enumerated().flatMap { $0.element.lastName == "Doe" ? $0.offset : nil }
= allThePeople.enumerated().flatMap { $1.lastName == "Doe" ? $0 : nil }
If you want the actual indices, use something like
struct User {
var firstName: String?
var lastName: String?
}
var allThePeople = [User(firstName: "John", lastName: "Doe"), User(firstName: "Jane", lastName: "Doe"), User(firstName: "John", lastName: "Travolta")]
var indices = [Int]()
for i in 0 ..< allThePeople.count {
if allThePeople[i].lastName == "Doe" {
indices.append(i)
}
}
indices // [0,1]
otherwise use filter as #Callam suggested.

Resources