Sorting Struct array in Swift 4 - ios

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)

Related

How to match users based on same Sets

I'm about to finish my App, similar to Tinder, but when I can't figure out how to match users based on their food likings, via their Sets of Food.
Is .filter() the way to go?
Video showing user selection of Food and CardView
CardStack will be the compilation of all CardView that will be presented to the user matched in its likings.
struct CardStack: View {
var people: [Person]
#State private var fullscreenMode: Bool = false
var body: some View {
ZStack {
ForEach(people) { person in
CardView(person: person, fullscreenMode: $fullscreenMode)
}
}
}
}
Meanwhile Person is the parameter taken on CardView, that the user will then match to.
struct Person: Hashable, Identifiable {
var id = UUID().uuidString
var foodLikings: Set<Food>
init(id: UUID) {
self.foodLikings = Set(arrayLiteral: Food(id: 3, name: "Empanadas", foodImage: ["empanadas"]), Food(id: 1, name: "Asado", foodImage: ["asado"]))
}
}
Here's what the Set<Food> consists of:
struct Food: Identifiable, Hashable {
var id: Int
let name: String
let foodImage: [String]
// Equatable
static func == (lhs: Food, rhs: Food) -> Bool {
lhs.id == rhs.id
}
}
All the available options for Food selection:
class FoodDataService {
static let comidas: [Comida] = [
Comida(
id: 0,
name: "Asado",
foodImage: ["asado"]
),
Food(
id: 1,
name: "Pizzas",
foodImage: ["pizzas"]
),
Food(
id: 2,
name: "Milanesas",
foodImage: ["milanesas"]
),
Food(
id: 3,
name: "Empanadas",
foodImage: ["empanadas"]
)
}
And finally what the User's struct is made of:
var person: Person
var user: User
struct User {
var foodLikings: Set<Food>
}
Found a much easier way to match users by simply using .contains().
if favoriteList.contains(personFavoriteList) {
// Show matched people
} else {
// Show other
}
Where personFavoriteList and favoriteList are strings returned as their likings mapped and sorted.

Don't know how to access array data inside dictionary [: []], Swift, iOS

I am stuck with this guy [ : []]. As you can see, currently inside function I am using [0] to access value, but with this type of solution I can only access first element of array. How can I access all values? I tried with for in loop, but couldn't solve it...
Thanks in advance!
func tripServices(tripId: String) {
networkManager.getBookedTripsDetails(endpoint: "/v1/bookings/\(tripId)", tripId: tripId) { [unowned self] (safeTripsDetails) in
DispatchQueue.main.async {
self.bookedTripDetails = self.createScreenData(from: safeTripsDetails!, singleTrip: self.singleTrip!)
self.tableView.reloadData()
}
}
}
func createScreenData(from data: [String: [BookingService]], singleTrip: BookedTripsForView) -> [DetailsAboutServiceForView] {
return data.map{ ( data) -> DetailsAboutServiceForView in
return DetailsAboutServiceForView(id: singleTrip.id,
destination: data.value[0].destination,
tripStatus: data.value[0].status,
tripStartTime: data.value[0].startDate,
serviceName: data.value[0].serviceName,
serviceId: data.value[0].serviceId)
}
}
If you have a dictionary of arrays, and you want your output to be a single array containing all of the arrays combined into one array of a different type, there are various ways you could do that.
Rather than trying to work out your data types, I banged out an example using simple structs:
//Source struct
struct Person {
let name: String
let age: Int
}
//Output struct
struct Employee {
let name: String
let age: Int
let salary: Int?
}
let dictOfArrays = ["key1": [Person(name: "Bob", age: 36),
Person(name: "Felecia", age: 27),
Person(name: "Armin", age: 19)],
"key2": [Person(name: "Janet", age: 57),
Person(name: "John", age: 12),
Person(name: "Karen", age: 43)]
]
//Create an empty array for the output
var output = [Employee]()
//Loop through the dictionaries
dictOfArrays.forEach { (_, values) in
values.forEach { person in
//Only those who are >=18 can be employees
if person.age >= 18 {
output.append( Employee(name: person.name, age: person.age, salary: nil))
}
}
}
//Logs the resulting array of Employee structs.
output.forEach { print($0) }
Edit:
As pointed out by Alexander in his comment, you can do the above in one statement without creating an array var and appending to it using a combination of flatmap, filter, and map:
let output = dictOfArrays.flatMap { (_, values) in
values.filter { $0.age >= 18 }
.map { person in
Employee(name: person.name, age: person.age, salary: nil)
}
}

How to extract one type of value from an array to create a separate array one by one?

I am trying to extract just the id's from the Properties Array to form a separate array. So far I have come up with this:
struct Interactions: View {
#State var A: [Properties] = [
.init(id: 5, name: "Five"),
.init(id: 8, name: "Eight"),
.init(id: 2, name: "Two")
]
var body: some View {
List(loadIdArray(), id: \.self) { i in
Text("\(i)")
}
}
func loadIdArray() -> [Int] {
let ids: [Int] = [1, 2]
for i in 0 ..< self.A.count {
let ids = [self.A[i].id, self.A[i + 1].id]
return ids
}
return ids
}
}
The problem is that I would have to manually type each [self.A[I + ...].id] which defeats the purpose of the function.
The answer I want is for loadIdArray() = [5, 8, 2] and for it to do this automatically depending on how many items are in the 'A' array.
how about using this:
func loadIdArray() -> [Int] {
return self.A.map { $0.id }
}

How to filter object-array with duplicated items name based on conditions in Swift

I have an object array with duplicated object properties value for some objects and I need to filter it according to some object properties conditions
I have something similar to the following code:
Model:
struct MyObject {
var name: String
var id: String
var times: Int
var value: Double
}
Array:
Let objectArray = [
MyObject(name: “Obj1”, id: “123”, times: 1, value: 3.0),
MyObject(name: “Obj2”, id: “456”, times: 1, value: 2.3),
MyObject(name: “Obj3”, id: “789”, times: 1, value: 1.0),
MyObject(name: “Obj2”, id: “456”, times: 2, value: 3.3),
MyObject(name: “Obj2”, id: “456”, times: 3, value: 4.7),
MyObject(name: “Obj4”, id: “212”, times: 1, value: 2.4)
]
I have implemented an extension Array function to remove the duplicated items in array but it's not enough on this case:
func filterDuplicate<T>(_ keyValue:(Element)->T) -> [Element] {
var uniqueKeys = Set<String>()
return filter{ uniqueKeys.insert("\(keyValue($0))").inserted }
}
I need to filter objectArray to obtain an array without duplicated objects with the condition to maintain the highest value for times property, that means, the final result should be:
Expected result:
filteredObjectArray = [
MyObject(name: “Obj1”, id: “123”, times: 1, value: 3.0),
MyObject(name: “Obj3”, id: “789”, times: 1, value: 1.0),
MyObject(name: “Obj2”, id: “456”, times: 3, value: 4.7),
MyObject(name: “Obj4”, id: “212”, times: 1, value: 2.4)
]
With Obj2 repeated item filtered by times property = 3
Here is a solution using reduce(into:) to filter out the unique objects
let uniqueAndHighest = objectArray.reduce(into: [MyObject]()) { array, object in
if let index = array.firstIndex(where: { $0.id == object.id }) {
if array[index].times < object.times {
array[index] = object
}
} else {
array.append(object)
}
}
and an alternative solution using Dictionary(grouping:)
let uniqueAndHighest = Dictionary(grouping: objectArray) {$0.id}
.values
.compactMap { $0.max {$0.times < $1.times}}
You can use reduce in this case:
let resultArray = objectArray.reduce([]) { (nextResult, obj) -> [MyObject] in
var tempArray = nextResult
if let existObj = tempArray.first(where: { $0.name == obj.name && $0.id == obj.id }) {
if obj.times > existObj.times {
tempArray.removeAll(where: { $0.name == existObj.name })
tempArray.append(obj)
}
} else {
tempArray.append(obj)
}
return tempArray
}

Convert loop into swift higher order function

I am writing an iOS App in Swift 4.2
I am looping through array 1 and making changes to its one element and adding those elements to array 2.
Array:
let array:[model] = []
let array2:[model] = []
Model for this array is:
class model: NSObject {
var id:Int = 0
var name:String=""
var isChecked:Bool=false
}
Function:
func customCheckboxValueChangedWithID(_ id: Int, isChecked: Bool) {
array2.removeAll()
//TODO, use sugar syntax/higher order function for this loop:
for element in array{
if(element.id==id){
element.isChecked=isChecked
if(element.isChecked){
array2.append(element)
}
}
}
reloadCollectionView()
}
How to convert this into swift higher order function, like map, flatMap, etc?
You can do it this way:
array.forEach { if $0.id == id { $0.isChecked.toggle() } }
If you just want to filter the elements and only leave the elements whose isChecked property set to true, you can use this:
let newArray = array.filter { $0.isChecked }
Since you have an array of classes, this is very simple:
let array: [model] = [...]
array.first { $0.id == id }?.isChecked = isChecked
If you had an array of structs, you would either need to update the original array or create a whole new array:
var array: [model] = []
// updating element in the original array
if let index = array.index(where: { $0.id == id }) {
array[index].isChecked = isChecked
}
// updating the whole array
array = array.map {
guard $0.id == id else { return $0 }
var element = $0
element.isChecked = isChecked
return element
}
You can rewrite this like...
array.forEach {
if $0.id == someID { $0.isChecked = isChecked }
}
This only works because you are using classes.
If you switch to using structs then you could have...
let newArray = array.map {
if $0.if == someID {
var theModel = $0
theModel.isChecked = isChecked
return theModel
}
return $0
}
how about using 'flatMap'
here is playground source
//: Playground - noun: a place where people can play
import UIKit
struct model {
var id:Int = 0
var name:String=""
var isChecked:Bool=false
}
let array:[model] = [model(id: 0, name: "0", isChecked: false),
model(id: 1, name: "1", isChecked: false),
model(id: 2, name: "2", isChecked: false),
model(id: 3, name: "3", isChecked: false),
model(id: 4, name: "4", isChecked: false),
model(id: 5, name: "5", isChecked: false),
model(id: 6, name: "6", isChecked: false)
]
var array2:[model] = []
func customCheckboxValueChangedWithID(_ id: Int, isChecked: Bool) {
array2 = array.flatMap { (ma: model) -> model? in
var element = ma
if element.id == id {
element.isChecked = isChecked
return element
}
return nil
}
}
customCheckboxValueChangedWithID(1, isChecked: true)
print("array2 : \(array2)")
result:
array2 : [__lldb_expr_53.model(id: 1, name: "1", isChecked: true)]
You can use Filter
numbers.forEach({ if $0.id == id { $0.isChecked = isChecked } })(Copied SPatel's comment)
Loops over a collection and returns an array that contains elements that meet a condition.
Let’s assume we need to filter even numbers only in the following array example. The traditional approach can be:
let numbersArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15]
// The traditional way
var evenNumArray: [Int] = []
for number in numbersArray {
if number % 2 == 0 {
evenNumArray.append(number)
}
}
// evenNumArray = [2, 4, 6, 8, 10, 14]
Instead, let’s use filter for the same result:
let numbersArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15]
// Filter
let evenArray = numbersArray.filter { $0 % 2 == 0 }
Simple Higher Order Functions in Swift
You can try this:
class MyObj: CustomStringConvertible {
var id: String
var isChecked: Bool
init(id: String = "", isChecked: Bool = false) {
self.id = id
self.isChecked = isChecked
}
var description: String {
return "MyObj(id: \(id), isChecked: \(isChecked)"
}
}
let array = [
MyObj(id: "id1"),
MyObj(id: "id1", isChecked: true),
MyObj(id: "id2"),
MyObj(id: "id3", isChecked: true)
]
func customCheckboxValueChanged(_ values: [MyObj], id: String, isChecked: Bool) -> [MyObj] {
let sameIDModels: [MyObj] = values
.filter { $0.id == id }
.map { $0.isChecked = isChecked; return $0 }
return sameIDModels.filter{ $0.isChecked }
}
let isChecked = true
let myID = "id1"
print(customCheckboxValueChanged(array, id: myID, isChecked: isChecked))

Resources