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)
}
Related
I am trying to filter an array of structs that has array. Below are the data structures I am using. I want the inner array filtered also but it doesn't work
var objects = [SomeObject]() //array of objects
var filteredObject = [SomeObject]() //filtered array
var isSearching = false
struct SomeObject {
var sectionName: String
var sectionObjects : [History]
}
struct History {
var firstName: String
var lastName: Int
}
func searchBar(_ text: String) {
filteredObject = objects.filter({ (obj: SomeObject) -> Bool in
return obj.sectionObjects.filter { $0.firstName.contains(text.lowercased())}.isEmpty
})
print("====", filteredObject, "fill===")
}
let history = History(firstName: "John", lastName: 1)
let anotherHistroy = History(firstName: "Dee", lastName: 2)
let historyArray = [history, anotherHistroy]
let newObject = SomeObject(sectionName: "Section 1", sectionObjects: historyArray)
objects.append(newObject)
searchBar("Jo") // printing of filtered object should not have another history in it's sectionObjects
You might be looking for something like this:
func searchBar(_ text: String) {
filteredObject = []
for var ob in objects {
ob.sectionObjects = ob.sectionObjects.filter {
$0.firstName.contains(text)
}
if !ob.sectionObjects.isEmpty {
filteredObject.append(ob)
}
}
print("====", filteredObject, "fill===")
}
Could perhaps be done more elegantly with reduce(into:), but on the whole it is best to start simply by saying exactly what you mean. You can tweak as desired to take account of case sensitivity.
What i want to do is when i start the app, save all the names (in userDefault) of the constants from all models. I plan on doing it with a function looking something like:
public static func setup(models: [Codable]) {
models.forEach { (model) in
let mirr = Mirror.init(reflecting: model)
mirr.children.map({
UserDefaults.save("\(type(of: model))+\($0.label!)")})
}
}
Please note that i'm not finished yet.
What i know of there are 3 solutions to using this method. As the mirror wont show me labels unless the variable / constant has an actual value. It becomes really ugly and i wonder if i can do some work around because current you call it like this.
Scenario A:
public struct Testing: Codable {
let name: String = ""
let sex: String = ""
}
setup(models: [Testing()])
Scenario B:
public struct testing: Codable {
let name: String
let sex: String
}
setup(models: [Testing(name: "", sex: "")])
Scenario C:
public struct testing: Codable {
let name: String
let sex: String
init(name: String = "", sex: String = "") {
self.name = name
self.sex = sex
}
}
setup(models: [Testing()])
So basically what i want to do is:
public struct testing: Codable {
let name: String
let sex: String
}
setup(models: [Testing()])
// or
setup(models: [Testing.self])
Or kinda anything that wont force me to init the values.
I guess it can't be done, but maybe someone have some hack out there that work...
Thanks in advance.
You can simply update your setup(models:) method to,
public func setup(models: [Codable]) {
let arr: [String] = models.compactMap {
let mirror = Mirror(reflecting: $0)
if let name = mirror.children.first(where: { $0.label == "name" }) {
let value = "\(type(of: $0))+\(name.value)"
return value
}
return nil
}
UserDefaults.standard.set(arr, forKey: "Names")
}
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.
I've already check all of those topics:
How to save an array of custom struct to NSUserDefault with swift?
How to save struct to NSUserDefaults in Swift 2.0
STRUCT Array To UserDefaults
I have a struct containing some Strings and an other struct: MySection.
struct MySection {
var name: String = ""
var values: [MyRow] = []
}
And there is MyRow which is store in MySection.values
struct MyRow {
var value: String = ""
var quantity: String = ""
var quantityType: String = ""
var done: String = ""
}
Two arrays for use it
var arraySection: [MySection] = []
var arrayRow: [MyRow] = []
And in my application, I add dynamically some values in those arrays.
There is the delegate method for get datas from my second ViewController
func returnInfos(newItem: [MyRow], sectionPick: String) {
arrayRow.append(MyRow())
arrayRow[arrayRow.count - 1] = newItem[0]
manageSection(item: sectionPick)
listTableView.reloadData()
}
And there is the manageSection function.
func manageSection(item: String) {
var i = 0
for _ in arraySection {
if arraySection[i].name == item {
arraySection.insert(MySection(), at: i + 1)
arraySection[i + 1].values = [arrayRow[arrayRow.count - 1]]
return
}
i += 1
}
arraySection.append(MySection())
arraySection[arraySection.count - 1].name = item
arraySection[arraySection.count - 1].values = [arrayRow[arrayRow.count - 1]]
}
My need is to store datas of the two arrays in UserDefaults (or CoreData maybe??) and use these datas when the user going back to the application.
I don't know how to do it, I've already try methods from the 3 topics but I'm not even doing a good job.
How can I do it?
Thanks guys!
Since both types contain only property list compliant types a suitable solution is to add code to convert each type to a property list compliant object and vice versa.
struct MySection {
var name: String
var values = [MyRow]()
init(name : String, values : [MyRow] = []) {
self.name = name
self.values = values
}
init(propertyList: [String: Any]) {
self.name = propertyList["name"] as! String
self.values = (propertyList["values"] as! [[String:String]]).map{ MyRow(propertyList: $0) }
}
var propertyListRepresentation : [String: Any] {
return ["name" : name, "values" : values.map { $0.propertyListRepresentation }]
}
}
struct MyRow {
var value: String
var quantity: String
var quantityType: String
var done: String
init(value : String, quantity: String, quantityType: String, done: String) {
self.value = value
self.quantity = quantity
self.quantityType = quantityType
self.done = done
}
init(propertyList: [String:String]) {
self.value = propertyList["value"]!
self.quantity = propertyList["quantity"]!
self.quantityType = propertyList["quantityType"]!
self.done = propertyList["done"]!
}
var propertyListRepresentation : [String: Any] {
return ["value" : value, "quantity" : quantity, "quantityType" : quantityType, "done" : done ]
}
}
After creating a few objects
let row1 = MyRow(value: "Foo", quantity: "10", quantityType: "Foo", done: "Yes")
let row2 = MyRow(value: "Bar", quantity: "10", quantityType: "Bar", done: "No")
let section = MySection(name: "Baz", values: [row1, row2])
call propertyListRepresentation to get a dictionary ([String:Any]) which can be saved to User Defaults.
let propertyList = section.propertyListRepresentation
Recreation of the section is quite easy, too
let newSection = MySection(propertyList: propertyList)
Edit
Use the propertyList initializer only if you get data from UserDefaults in all other cases use the other initializer.
For example replace
#IBAction func addButtonPressed(_ sender: Any) {
newProducts.append(MyRow(propertyList: ["":""]))
newProducts[newProducts.count - 1].value = nameTextField.text!
newProducts[newProducts.count - 1].quantity = quantityTextField.text!
newProducts[newProducts.count - 1].quantityType = type
newProducts[newProducts.count - 1].done = "No"
delegate?.returnInfos(newItem: newProducts, sectionPick: typePick)
navigationController?.popViewController(animated: true)
}
with
#IBAction func addButtonPressed(_ sender: Any) {
let row = MyRow(value: nameTextField.text!,
quantity: quantityTextField.text!,
quantityType: type,
done: "No")
newProducts.append(row)
delegate?.returnInfos(newItem: newProducts, sectionPick: typePick)
navigationController?.popViewController(animated: true)
}
and replace
func returnInfos(newItem: [MyRow], sectionPick: String) {
arrayRow.append(MyRow(propertyList: ["":""]))
arrayRow[arrayRow.count - 1] = newItem[0]
manageSection(item: sectionPick)
listTableView.reloadData()
}
with
func returnInfos(newItem: [MyRow], sectionPick: String) {
arrayRow.append(newItem[0])
manageSection(item: sectionPick)
listTableView.reloadData()
}
Basically first create the object, then append it to the array. The other way round is very cumbersome.
How do you initialize your classes/structs with a lot of properties?
This question could probably be asked without Swift context but Swift brings a flavour to it, so I add Swift tag in headline and tags.
Let's say you have a User class with 20 properties. Most of them should not be nil or empty. Let's assume these properties are not interdependent. Let's assume that 33% of it should be constant (let) by the logic of the class. Let's assume that at least 65% of them do not have meaningful default values. How would you design this class and initialize an instance of it?
So far I have few thoughts but none of it seems to be completely satisfactory to me:
put all of the properties linearly in the class and make huge init method:
class User {
// there is 20 properties like that
let id : String
let username : String
let email : String
...
var lastLoginDate : Date
var lastPlayDate : Date
// then HUUUUGE init
init(id: String,
username: String,
...
lastPlayDate: Date) {
}
}
try to group properties into sub types and deal with smaller inits separately
class User {
struct ID {
let id : String
let username : String
let email : String
}
struct Activity {
var lastLoginDate : Date
var lastPlayDate : Date
}
let id : ID
...
var lastActivity : Activity
// then not so huge init
init(id: ID,
...
lastActivity: Activity) {
}
}
another solution is to break requirements a bit: either declare some of the properties optional and set values after init or declare dummy default values and set normal values after init, which conceptually seems to be the same
class User {
// there is 20 properties like that
let id : String
let username : String
let email : String
...
var lastLoginDate : Date?
var lastPlayDate : Date?
// then not so huge init
init(id: String,
username: String,
email: String) {
}
}
// In other code
var user = User(id: "1", username: "user", email: "user#example.com"
user.lastLoginDate = Date()
Is there a nice paradigm/pattern how to deal with such situations?
You can try the builder pattern.
Example
class DeathStarBuilder {
var x: Double?
var y: Double?
var z: Double?
typealias BuilderClosure = (DeathStarBuilder) -> ()
init(buildClosure: BuilderClosure) {
buildClosure(self)
}
}
struct DeathStar : CustomStringConvertible {
let x: Double
let y: Double
let z: Double
init?(builder: DeathStarBuilder) {
if let x = builder.x, let y = builder.y, let z = builder.z {
self.x = x
self.y = y
self.z = z
} else {
return nil
}
}
var description:String {
return "Death Star at (x:\(x) y:\(y) z:\(z))"
}
}
let empire = DeathStarBuilder { builder in
builder.x = 0.1
builder.y = 0.2
builder.z = 0.3
}
let deathStar = DeathStar(builder:empire)
Example taken from here: https://github.com/ochococo/Design-Patterns-In-Swift
If you are looking for a bit more Java like solution, you can try something like this.
Alternative Example
final class NutritionFacts {
private let servingSize: Int
private let servings: Int
private let calories: Int
private let fat: Int
private let sodium: Int
private let carbs: Int
init(builder: Builder) {
servingSize = builder.servingSize
servings = builder.servings
calories = builder.calories
fat = builder.fat
sodium = builder.sodium
carbs = builder.carbs
}
class Builder {
let servingSize: Int
let servings: Int
private(set) var calories = 0
private(set) var fat = 0
private(set) var carbs = 0
private(set) var sodium = 0
init(servingSize: Int, servings: Int) {
self.servingSize = servingSize
self.servings = servings
}
func calories(value: Int) -> Builder {
calories = value
return self
}
func fat(value: Int) -> Builder {
fat = value
return self
}
func carbs(value: Int) -> Builder {
carbs = value
return self
}
func sodium(value: Int) -> Builder {
sodium = value
return self
}
func build() -> NutritionFacts {
return NutritionFacts(builder: self)
}
}
}
let facts = NutritionFacts.Builder(servingSize: 10, servings: 1)
.calories(value: 20)
.carbs(value: 2)
.fat(value: 5)
.build()
Example taken from: http://ctarda.com/2017/09/elegant-swift-default-parameters-vs-the-builder-pattern