How to retrieve results from Struct in array - swift

I have an enum and Struct that looks like the following.
enum Position: String {
case lw = "Left Wing"
case rw = "Right Wing"
case c = "Centre"
case d = "Defense"
case g = "Goalie"
}
struct PlayerInformation {
let firstName: String
let lastName: String
let position: Position
let number: Int
}
struct ShiftDetails {
let player: PlayerInformation
var timeOnIce: Int
var dateOnIce: Date
}
I build the playerInformationArray with the following:
var playerInformationArray = [PlayerInformation]()
let sidneyCrosby = PlayerInformation.init(firstName: "Sidney", lastName: "Crosby", position: Position.c, number: 87)
let alexOvechkin = PlayerInformation.init(firstName: "Alex", lastName: "Ovechkin", position: Position.lw, number: 8)
let patrickKane = PlayerInformation.init(firstName: "Patrick", lastName: "Kane", position: Position.rw, number: 88)
playerInformationArray.append(sidneyCrosby)
playerInformationArray.append(alexOvechkin)
playerInformationArray.append(patrickKane)
I store information about a players "shift" in the following array:
var shiftDetails = [ShiftDetails]()
I retrieve the information about the "selected" player from a collection view with the following:
let selectedPlayer = playerInformationArray[indexPath.row]
I then update the shiftDetails array with the following:
shiftDetails.append( ShiftDetails.init(player: selectedPlayer, timeOnIce: timerCounter, dateOnIce: Date()) )
Everything works as excepted, but I'm having a hard time understanding, how to retrieve data from the arrays. For example:
How would I retrieve the count of shiftDetails per player?
How would I retrieve the sum of timeOnIce per player?
Thanks!

If you need to check if certain player is equal to another player you need to make it conform to Equatable protocol implementing the double equal operator ==. You can also make your struct conform to `CustomStringConvertible and provide a custom description to it:
struct Player: Equatable, CustomStringConvertible {
let firstName: String
let lastName: String
let position: Position
let number: Int
static func ==(lhs: Player, rhs: Player) -> Bool {
return lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName
}
var description: String {
return "Player: " + firstName + " " + lastName
}
}
Also in Swift you should always prefer long names (camelCase) for better readability and try to avoid redundant information when naming your properties and you shouldn't add the type to your object names:
enum Position: String {
case leftWing = "Left Wing"
case rightWing = "Right Wing"
case center = "Center"
case defense = "Defense"
case golie = "Goalie"
}
struct Shift {
let player: Player
var timeOnIce: Int
var dateOnIce: Date
}
var players: [Player] = []
let sidneyCrosby = Player(firstName: "Sidney", lastName: "Crosby", position: .center, number: 87)
let alexOvechkin = Player(firstName: "Alex", lastName: "Ovechkin", position: .leftWing, number: 8)
let patrickKane = Player(firstName: "Patrick", lastName: "Kane", position: .rightWing, number: 88)
players += [sidneyCrosby, alexOvechkin, patrickKane]
var shifts: [Shift] = []
var index = IndexPath(row: 0, section: 0)
var selectedPlayer = players[index.row]
let shift1 = Shift(player: selectedPlayer, timeOnIce: 3, dateOnIce: Date())
shifts.append(shift1)
let shift2 = Shift(player: selectedPlayer, timeOnIce: 5, dateOnIce: Date())
shifts.append(shift2)
To sum timeOnIce property per player and its count, you could extend Array constraining the elements to Shift type:
extension Array where Element == Shift {
func timeOnIceAndCount(for player: Player) -> (timeOnIce: Int, count: Int) {
return reduce((0,0)) {
$1.player == player ? ($0.0 + $1.timeOnIce, $0.1 + 1) : $0
}
}
}
let (timeOnIce, count) = shifts.timeOnIceAndCount(for: selectedPlayer)
print(selectedPlayer)
print("TimeOnIce:",timeOnIce)
print("Count: ", count)
This will print
Player: Sidney Crosby
TimeOnIce: 8
Count: 2

This may not end up being the cleanest way of doing this since all your shiftDetails are held in a single, shared array, but in order to accomplish this with your current setup you could use the built-in filter function to get information about a given player. This could look something like this:
let selectedPlayer = playerInformationArray[indexPath.row]
let filteredDetails = shiftDetails.filter({ $0.player == selectedPlayer })
let shiftCount = filteredDetails.count
let timeOnIce = filteredDetails.reduce(0, { $0 + $1.timeOnIce })
Another option would be to maintain a shiftDetails array for each player, at which point you don't need to do any filtering. Or, another option would be to have the shiftDetails be a dictionary where the key is the player and the value is an array of shiftDetails.
It is also worth noting that my example of the filter function assumes your Player struct conforms to the Equatable protocol - in other words, that you can legally say playerOne == playerTwo. If you don't do this you would have to match on some other unique field such as name or playerId or something like that.

Related

How to add value from object in dictionary in Swift?

I have dictionary which is having objects as value, I want to retrieve value from object.
code :
public class Student {
public let name: String
public let age: Double
public init(name: String, age: Double) {
self.name = name
self.age = age
}
}
and dictionary as ["1": Student, "2" : Student ]
How can i add all the ages of student? for 13 + 12.
Here are potential solutions:
let dict = ["1": Student(name: "A", age: 13), "2" : Student(name: "B", age: 12)]
Side note, there is nothing wrong in doing a manual for loop. It's basic algorithm skills, and it's need to understand higher level methods.
Basic for loop:
var sumOfAges: Double = 0
for (_, value) in dict {
sumOfAges += value.age
}
print(sumOfAges)
Basic forEach loop
var sumOfAges2: Double = 0
dict.forEach {
sumOfAges2 += $0.value.age
}
With reduce(into:_:):
let sumOfAges3: Double = dict.reduce(into: 0) { partialResult, aKeyValue in
partialResult += aKeyValue.value.age
}
With reduce(into:_:), and using Shorthand Argument Names (which make sense only if you understand the previous step):
let sumOfAges4 = dict.reduce(into: 0, { $0 += $1.value.age })
Side note, since the output is just a number, reduce(_:_:) is enough:
let sumOfAges5 = dict.reduce(0) { $0 + $1.value.age })
Additional possible step, is to transform your dictionary into a simpler data, like an array of Double.
let allAges = dict.mapValues { $0.age }
// or let allAges = dict.mapValues { \.age }
// or let allAges = dict.values.map { $0.age }
// or let allAges = dict.values.map { \.age }
// ...
You have an array of Double, then, use either a for loop, forEach, reduce(into:_) again on this "simplified array"
The downside, is that you iterate twice, one for the mapping, one for your calculation.
Edit: Added commented alternative solutions by #Leo Dabus

Filter array of nested structs

I'm looking for a nice swift solution for the following problem:
Lets say we have 2 structs like so:
struct Person {
let name: String
let age: Int
let skills: Skills
init(name: String, age: Int, skills: Skills) {
self.name = name
self.age = age
self.skills = skills
}
}
struct Skills {
let canUseBow: Bool
let canUseSword: Bool
let canUseShield: Bool
init(canUseBow: Bool, canUseSword: Bool, canUseShield: Bool) {
self.canUseBow = canUseBow
self.canUseSword = canUseSword
self.canUseShield = canUseShield
}
}
Now lets say I have an array of Person where each person has their own skills obviously where the corrosponding values can be true or false.
Lets say I want another array of just people that have the skill canUseBow as true so that skill must be set to true , how would I go about filtering out the Persons that do not have canUseBow set to true?
I was thinking in a direction of:
filteredPersons = persons.filter {
$0.skills
}
But that way it would require me to than select something after skills for example
$0.skills.canUseBow
That does not seem very future proof, lets say I would want to add more skills than I would also have to change the filter method again. Are there better ways to go about this?
You can try this with an OptionSet that can hold all of these flags for you in a simple Int storage.
import Foundation
struct Person {
let name: String
let age: Int
let skills: Skills
init(name: String, age: Int, skills: Skills) {
self.name = name
self.age = age
self.skills = skills
}
}
struct Skills: OptionSet {
let rawValue: Int
init(rawValue: Int) {
self.rawValue = rawValue
}
static let canUseBow = Skills(rawValue: 1 << 0)
static let canUseSword = Skills(rawValue: 1 << 1)
static let canUseShield = Skills(rawValue: 1 << 2)
init(json: [String: Bool]) {
var skills = Skills(rawValue: 0)
if let canUseBow = json["can_use_bow"], canUseBow {
skills.insert(.canUseBow)
}
if let canUseSword = json["can_use_sword"], canUseSword {
skills.insert(.canUseSword)
}
if let canUseShield = json["can_use_shield"], canUseShield {
skills.insert(.canUseShield)
}
self = skills
}
}
How to instantiate Skills?
let skills = Skills(json: [
"can_use_bow" : true,
"can_use_sword" : true,
"can_use_shield" : false,
])
How to filter based on multiple skills?
let targetSkills: Skills = [.canUseBow, .canUseSword]
let persons: [Person] = []
let filteredPersons = persons.filter {
targetSkills.isSubset(of: $0.skills)
}

How to sort array with multiple criteria including the value of enum, names and numbers?

I want to sort this array that i have named lists, which includs Player with Positions, what i want to achive is if the one value of Player is nill, for example either "position" in PLayer or year,, then it should be the last element of the array, and enum values according descending order and sort it
example: when i print the first element of array, it should print like names: "Teddy", "Joh", "Alex", "Sia", "Adix", "Javi", "Moris", "Yosa", "Leo", "Davi", "Cars".?
can someone help me to build the sorted func works as I want?
Here is my enum are the just priorities.
enum Position {
case goalKeeper
case defender
case midfield
case forward
}
this is my Player struct
struct Player {
var name: String
var backNumber: Int?
var position: Position?
}
and this is my list of players
let list = [
Player(name: "Abbie", backNumber: nil, position: .defender),
Player(name: "Tom", backNumber: 99, position: .goalKeeper),
Player(name: "Carlos", backNumber: 88, position: nil),
Player(name: "Javier", backNumber: 32, position: .midfield),
Player(name: "Adam", backNumber: 32, position: .midfield),
Player(name: "Luis", backNumber: 16, position: .forward),
Player(name: "John", backNumber: 4, position: .defender),
Player(name: "Morike", backNumber: 10, position: .forward),
Player(name: "Silva", backNumber: 24, position: .midfield),
Player(name: "Yoshida", backNumber: 10, position: .forward),
Player(name: "David", backNumber: 8, position: nil)
]
and I want to sort it according the positions of the players in descending order
expected output will be like
print("\n\(["Tom", "John", "Abbie", "Silva", "Adam", "Javier", "Morike", "Yoshida", "Luis", "David", "Carlos"])")
this output above is supposed to achive using sorted method and depending on the positions of the players in descending order, Tom is goalkeeper so in the array it will be the first element, John is midfield ....... Morika and Yoshida is forward and so on,
You can sort your array using the following code
let sorted = list.sorted {
let firstPositionValue = $0.position?.rawValue ?? Int.max
let secondPositionValue = $1.position?.rawValue ?? Int.max
if firstPositionValue == secondPositionValue { // sort by back number or name
let firstBackNumber = $0.backNumber ?? Int.max
let secondBackNumber = $1.backNumber ?? Int.max
if firstBackNumber == secondBackNumber { // sort by name
return $0.name < $1.name
}
return firstBackNumber < secondBackNumber // sort by back number
}
return firstPositionValue < secondPositionValue // // sort by position
}
but to be able to sort by the enum the items needs to have a value, the following change will assign each item an increasing int value starting at 1
enum Position: Int {
case goalKeeper = 1
case defender
case midfield
case forward
}
If you want to you can assign the sort code to a variable
let sortFunction: (Player, Player) -> Bool = {
let firstPositionValue = $0.position?.rawValue ?? Int.max
let secondPositionValue = $1.position?.rawValue ?? Int.max
//... code omitted for brevity
let sorted = list.sorted(by: sortFunction)
}
and use it like this to perhaps make the code more readable
let sorted = list.sorted(by: sortFunction)
Yet another option is to make the Player type itself sortable, to do this the type needs to conform to the Comparable protocol
struct Player: Comparable {
static func < (lhs: Player, rhs: Player) -> Bool {
let firstPositionValue = lhs.position?.rawValue ?? Int.max
let secondPositionValue = rhs.position?.rawValue ?? Int.max
if firstPositionValue == secondPositionValue { // sort by back number or name
let firstBackNumber = lhs.backNumber ?? Int.max
let secondBackNumber = rhs.backNumber ?? Int.max
if firstBackNumber == secondBackNumber { // sort by name
return lhs.name < rhs.name
}
return firstBackNumber < secondBackNumber // sort by back number
}
return firstPositionValue < secondPositionValue
}
//... rest of code
}
then we can sort the array in an even simpler way
let sorted = list.sorted()
Easiest solution is update the model where you will add new variable & have calculation for this variable.
struct Player {
var name: String
var backNumber: Int?
var position: Position?
var sortingVariable : Int? {
get {
if (position == nil || backNumber == nil || (name ?? "").isEmpty) {
return 99 // let's say max limit is 99
}
if (position == goalkeeper) {
return 1
}
if (position == defender) {
return 2
} // .... & so on...
return 999 // dummy for nothing...
}
}
}
Now sort the array based on sortingVariable
var sortedArray = yourArrayList.sorted({ $0.sortingVariable > $1.sortingVariable })
This way, you can update logic at one place and can be used at any place for sorting.
Hope this is clear.

Merge objects of the same type

Say I have a struct Coin
struct Coin {
var value: Float?
var country: String?
var color: String?
}
I have two instances of a Coin; we'll call them coinA and coinB.
let coinA = Coin()
coinA.value = nil
coinA.country = "USA"
coinA.color = "silver"
let coinB = Coin()
coinB.value = 50.0
Now, I want to merge the values of coinB into coinA. So the result would be coinA whose values would result in:
country = "USA"
color = "silver"
value = 50.0
I am able to accomplish this with Dictionary objects using the merge() function. However, I am unsure how to accomplish this using custom Swift objects. Is there a way?
Update
Here's how I've gotten it to work with dictionaries:
var originalDict = ["A": 1, "B": 2]
var newDict = ["B": 69, "C": 3]
originalDict.merge(newDict) { (_, new) in new }
//originalDict = ["A": 1, "B": 69, "C": 3]
And I will further clarify, in this function if the newDict does not have keys that the originalDict, the originalDict maintains them.
Ultimately, the most efficient way in the fewest lines of code is probably exactly what you'd expect:
extension Coin {
func merge(with: Coin) -> Coin {
var new = Coin()
new.value = value ?? with.value
new.country = country ?? with.country
new.color = color ?? with.color
return new
}
}
let coinC = coinA.merge(with: coinB)
Note that in the above scenario, the resulting value will always be coinA's, and will only be coinB's if coinA's value for a given key is nil. Whenever you change, add, or delete a property on Coin, you'll have to update this method, too. However, if you care more about future-proofing against property changes and don't care as much about writing more code and juggling data around into different types, you could have some fun with Codable:
struct Coin: Codable {
var value: Float?
var country: String?
var color: String?
func merge(with: Coin, uniquingKeysWith conflictResolver: (Any, Any) throws -> Any) throws -> Coin {
let encoder = JSONEncoder()
let selfData = try encoder.encode(self)
let withData = try encoder.encode(with)
var selfDict = try JSONSerialization.jsonObject(with: selfData) as! [String: Any]
let withDict = try JSONSerialization.jsonObject(with: withData) as! [String: Any]
try selfDict.merge(withDict, uniquingKeysWith: conflictResolver)
let final = try JSONSerialization.data(withJSONObject: selfDict)
return try JSONDecoder().decode(Coin.self, from: final)
}
}
With that solution, you can call merge on your struct like you would any dictionary, though note that it returns a new instance of Coin instead of mutating the current one:
let coinC = try coinA.merge(with: coinB) { (_, b) in b }
I thought it would be interesting to show a solution based on Swift key paths. This allows us to loop somewhat agnostically through the properties — that is, we do not have to hard-code their names in a series of successive statements:
struct Coin {
var value: Float?
var country: String?
var color: String?
}
let c1 = Coin(value:20, country:nil, color:"red")
let c2 = Coin(value:nil, country:"Uganda", color:nil)
var c3 = Coin(value:nil, country:nil, color:nil)
// ok, here we go
let arr = [\Coin.value, \Coin.country, \Coin.color]
for k in arr {
if let kk = k as? WritableKeyPath<Coin, Optional<Float>> {
c3[keyPath:kk] = c1[keyPath:kk] ?? c2[keyPath:kk]
} else if let kk = k as? WritableKeyPath<Coin, Optional<String>> {
c3[keyPath:kk] = c1[keyPath:kk] ?? c2[keyPath:kk]
}
}
print(c3) // Coin(value: Optional(20.0), country: Optional("Uganda"), color: Optional("red"))
There are unfortunate features of key paths that require us to cast down from the array element explicitly to any possible real key path type, but it still has a certain elegance.
If you're willing to make the merge function specific to Coin, you can just use the coalesce operator like so:
struct Coin {
var value: Float?
var country: String?
var color: String?
func merge(_ other: Coin) -> Coin {
return Coin(value: other.value ?? self.value, country: other.country ?? self.country, color: other.color ?? self.color)
}
}
let coinC = coinA.merge(coinB)
This will return a new Coin using the values from coinB, and filling in any nils with those from coinA.
If your goal is to change coin A what you need is a mutating method. Note that structures are not like classes. If you would like to change its properties you need to declare your coin as variable. Note that none of your examples would compile if you declare your coins as constants:
struct Coin {
var value: Float?
var country: String?
var color: String?
mutating func merge(_ coin: Coin) {
value = value ?? coin.value
country = country ?? coin.country
color = color ?? coin.color
}
init(value: Float? = nil, country: String? = nil, color: String? = nil) {
self.value = value
self.country = country
self.color = color
}
}
Playground testing:
var coinA = Coin(country: "USA", color: "silver")
coinA.merge(Coin(value: 50))
print(coinA.country ?? "nil") // "USA"
print(coinA.color ?? "nil") // "silver"
print(coinA.value ?? "nil") // 50.0
This is not a high-level approach like the merge one you shared the link to but as long as you have a struct to implement the merge feature into, it will do the job.
func merge(other: Coin, keepTracksOfCurrentOnConflict: Bool) -> Coin {
var decidedValue = value
if decidedValue == nil && other.value != nil {
decidedValue = other.value
} else if other.value != nil {
//in this case, it's conflict.
if keepTracksOfCurrentOnConflict {
decidedValue = value
} else {
decidedValue = other.value
}
}
var resultCoin = Coin(value: decidedValue, country: nil, color: nil)
return resultCoin
}
}
You can do the same for other properties.
If you want to wrap it around protocol. The idea behind is the same:
you convert object's to dict
merge two dict's
convert merged dict back to your object
import Foundation
protocol Merge: Codable {
}
extension Dictionary where Key == String, Value == Any {
func mergeAndReplaceWith(object: [Key: Value]) -> [Key: Value] {
var origin = self
origin.merge(object) { (_, new) in
new
}
return origin
}
}
extension Merge {
func toJson() -> [String: Any] {
let jsonData = try! JSONEncoder().encode(self)
let json = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any]
return json
}
func merge(object: Merge) -> Merge {
let origin = self.toJson()
let objJson = object.toJson()
let decoder = JSONDecoder()
let merge = origin.mergeAndReplaceWith(object: objJson)
var jsonData = try! JSONSerialization.data(withJSONObject: merge, options: .prettyPrinted)
var mergedObject = try! decoder.decode(Self.self, from: jsonData)
return mergedObject
}
}
struct List: Merge {
let a: String
}
struct Detail: Merge {
struct C: Codable {
let c: String
}
let a: String
let c: C?
}
let list = List(a: "a_list")
let detail_without_c = Detail(a: "a_detail_without_c", c: nil)
let detail = Detail(a: "a_detail", c: Detail.C(c: "val_c_0"))
print(detail.merge(object: list))
print(detail_without_c.merge(object: detail))
Detail(a: "a_list", c: Optional(__lldb_expr_5.Detail.C(c: "val_c_0")))
Detail(a: "a_detail", c: Optional(__lldb_expr_5.Detail.C(c: "val_c_0")))
With this solution you can actually merge two representations of your endpoint, in my case it is List and Detail.

Cannot assign through subscript to Swift String

I have a class that contains a name, an image, a dashed form of the name, and the length of the name. For example, I could have "dog", an image of a dog, "---", and name length 3.
I just want to set name and pic for each object and have dashName and nameLength set automatically.
class Answer {
var name = "name"
var image: UIImage?
var dashName = "name"
var nameLength = 0
init(){
var a = 0
nameLength = name.characters.count
while a <= nameLength {
if (name[a] == " ") {dashName[a] = " "}
else {dashName[a] = "-"}
a += 1
}
}
}
The problem is the error that says: "cannot assign through subscript: subscript is get-only" and another error that says: "subscript is unavailable: cannot subscript String with an Int"
Because String's subscript operator is get-only, use map method instead, like:
class Answer {
var name = "name"
var image: UIImage?
var dashName = "name"
var nameLength = 0
init(){
dashName = String(name.map {$0 == " " ? " " : "-"})
}
}
As mentioned before,
Swift's String class is what other languages call a StringBuilder class, and for performance reasons, Swift does NOT provide setting character by index; If you don't care about performance a simple solution could be:
public static func replace(_ string: String, at index: Int, with value: String) {
let start = string.index(string.startIndex, offsetBy: index)
let end = string.index(start, offsetBy: 1)
string.replaceSubrange(start..<end, with: value)
}
Or as an extension:
extension String {
public func charAt(_ index: Int) -> Character {
return self[self.index(self.startIndex, offsetBy: index)];
}
public mutating func setCharAt(_ index: Int, _ new: Character) {
self.setCharAt(index, String(new))
}
public mutating func setCharAt(_ index: Int, _ new: String) {
let i = self.index(self.startIndex, offsetBy: index)
self.replaceSubrange(i...i, with: new)
}
}
Note how above needs to call index(...) method to convert integer to actual-index!? It seems, Swift implements String like a linked-list, where append(...) is really fast, but even finding the index (without doing anything with it) is a linear-time operation (and gets slower based on concatenation count).
The subscript operator for String is get-only, which means you can only read from a string using it, and have to use something else to write to a mutable String.
You can solve this issue, and clean up the code by using a map function on name
Swift 4
class Answer {
var name = "name"
var image: UIImage?
var dashName = "name"
var nameLength = 0
init()
{
nameLength = name.count
dashName = name.map { $0 == " " ? " " : "-" }.joined()
}
}
Swift 3
class Answer {
var name = "name"
var image: UIImage?
var dashName = "name"
var nameLength = 0
init()
{
nameLength = name.characters.count
dashName = name.characters.map { $0 == " " ? String(" ") : String("-") }.joined()
}
}