Swift Dictionary using subset of Array of Struct - arrays

I am trying to create a dictionary from an array of struct using grouping but I am not having much luck (I found a way iterating over every record but was hoping for a more elegant solution)...
var eventRecords = [EventRecord]()
and the structure looks like this (used to store records in CloudKit)
public struct EventRecord {
public var evDate: Date
public var evType: Int
public var evMainTxt: String
public var evNote: String?
public var evVal1: Int?
public var evVal2: Int?
}
what I am hoping to accomplish is a dictionary where the key is evType and the value is [evMainTxt]. the dict is defined as
var suggestionsDict = [Int: [String]]()
I started with Dictionary(grouping: eventRecords, by: {$0.evType}) but I am strugling with the .map portion of this statement Dictionary(grouping: eventRecords, by: {$0.evType}).map since I only want a subset of the EventRecord struct. Can it be done short of iterating over all the records? Since I am new to this, any advice will be appreciated.

The easiest way to do this is to use reduce(into:) so you can map and group in one step
let suggestionsDict = eventRecords.reduce(into: [:]) {
$0[$1.evType, default: []].append($1.evMainTxt)
}

Related

Problem on RealmSwift: "Invalid array input: more values (1) than properties (0)." while trying to persist an Array of String

I'm trying to persist in a table view cell, the result of a quiz test with questions and I needed the array of answers given (String Array) so I decided to use RealmSwift.
I created this class and of course I created also a RealmString object in the same file to handle the possibility to persist arrays of String in Realm in this way:
class RealmString: Object {
dynamic var stringValue = ""
}
class Test: Object {
#objc dynamic var ID = UUID().uuidString
#objc dynamic var testScore : String = String()
#objc dynamic var testTitle : String = String()
#objc dynamic var testSubTitle : String = String()
#objc dynamic var dateOfExecution: String = String()
#objc dynamic var answersGiven: [String] {
get {
return _backingAnswersGiven.map { $0.stringValue }
}
set {
_backingAnswersGiven.removeAll()
_backingAnswersGiven.append(objectsIn: (newValue.map({ RealmString(value: [$0]) })))
}
}
let _backingAnswersGiven = List<RealmString>()
override static func ignoredProperties() -> [String] {
return ["answersGiven"]
}
override static func primaryKey() -> String? {
return "ID"
}
Now in the view controller:
I have a variable that stores the result (is an Int array that will take ten answers with values from 0 to 5, and these will later be converted to String)
i.e.: [0,2,2,3,4,5,2,1,0,2] -> ["0","2","2","3","4","5","2","1","0","2"]
and when an option is selected in a question the value is set with this function, everything works fine.
public var questionResults: [Int] = []
func setValueToQuestion(questionNumber: Int) {
questionResults[questionNumber] = optionChosen
}
When the test is completed successfully everything is saved in this way:
let test = Test()
test.ID = currentTest?.ID ?? UUID().uuidString
test.testTitle = testTitleLabel.text!
test.testScore = resultNumberLabel.text!
test.testSubTitle = resultLabel.text!
test.dateOfExecution = dateTimeString
test.answersGiven = questionResults.map({String($0)})
DBManager.sharedInstance.addData(object: test)
I tried the code separately also adding breakpoints and everything works in the flow, expect this line:
test.answersGiven = questionResults.map({String($0)})
that raises the error shown in the title: "Invalid array input: more values (1) than properties (0)."
I guess it can be an error of mapping maybe?
This value is then treated in the rest of flow as a simple swift array of String = [String]
There are a few issues which may be leading to that error.
First the RealmString property is not persisted because it needs #objc
dynamic var stringValue = ""
should be
#objc dynamic var stringValue = ""
Secondly, and this is important, Realm does not support primitives in Lists. Well, it kinda does but not very well.
EDIT: Release 10.7 added support for filters/queries as well as aggregate functions on primitives so the below info is no longer completely valid. However, it's still something to be aware of.
See my answer to this question but in a nutshell, you need another class to store the string in - kind of like your RealmString class.
class StringClass: Object {
#objc dynamic var myString = ""
}
and then change the Test object property to use the StringClass property
#objc dynamic var answersGiven = [StringClass]()
and then I see you're trying to use a backed var and computed property but I am not sure why. It may be simpler to use use the var itself
let _backingAnswersGiven = List<RealmString>()
since the List collection already handles what's being computed.
For example, if you set the list you can set it to another list (which wipes out the current list). Or when you get the list let myStringList = test._backingAnswersGiven, gets all of the StringClasses in the list without having to .map over them.

SWIFT: assigning all values of an item in a structure array to a variable

Sorry, I don't even have an idea of the keywords to search for answer.
I want to store all items within a global structure to a local variable.
struct HighScores: Codable {
var highscoreRecord: [HighscoreRecord]
}
struct HighscoreRecord: Codable {
var Rank:Int
var Date:Date
var avDuration:Float
var Score:Int
}
A global variable is based on this structure and populated within a UIViewController
var jsonResult: HighScores?
Now, in another UIViewController, I want to extract the values of Score for all Highscores and store it to a local variable. I thought it should look somewhat like this, however, I do not get it to work
#IBDesignable class ScoreTimeGraphView: UIView {
var graphScore = jsonResult!.highscoreRecord.Score
The declaration above throws "Value of type '[HighscoreRecord]' has no member 'Score'"
Any ideas how to do this?
Cheers!
highscoreRecord is an Array. You can't use .Score directly on it because the Array type doesn't have a property named Score.
However, because it's element type is HighScore (which does have the property you want), you can iterate over it and collect the Score property from each one.
I think this is what you are after:
var allGraphScores = jsonResult!.highscoreRecord.map { $0.Score }
.map(_:) takes a closure with one parameter, and passes in each element of a sequence in turn.
So, highscoreRecord.map { $0.Score } returns a new array, by finding the Score property of each HighScoreRecord in the array highscoreRecord.
PS it's probably a good idea to name your variables using lowercase camelCase, for readability and instant recognition by any Swift dev that Score is a variable and not an object.
You're trying to access the property from an array. You need to provide an index to remove the error. Update this line:
var graphScore = jsonResult!.highscoreRecord.Score
To this:
var graphScore = jsonResult!.highscoreRecord[0].Score

Swift - Sort array similar to another

In my tableview I get my data shown from these arrays:
var offersProduct = [String]()
var offersPrice = [String]()
var savings = [String]()
var shops = [String]()
var imageNames = [String]()
var exDates = [Int]()
var pickedIDs = [String]()
var setDates = [Int]()
Right now the data isn't sorted in any way. So I have 2 questions.
How do I sort arrays after date? Nearest to NSDate first.
I have the array of exDates, witch is an array of Int of timeIntervalSince1970. So i want the closest expiry date to get index 0 of the array.
How do I sort the other arrays like the first one?
So, lets say I have sorted exDates array, that gives me another problem, cause now the expiry date shown, doesn't match with the data from the other arrays. So I need to somehow sort the other arrays, by moving the data in from the same index as in the exDates array. How can that be done?
Hope someone can help.
Your problem is that you use separate entities where single/uniform entity should be used. To achieve what you are trying, you should switch to using class or struct which would encompass all the associated data.
If you proceed with the approach you currently adopt, you are guaranteed to have arbitrary bugs and performing dull operations to synchronize data across all your arrays. So, in essence, having multiple arrays instead of one array of specialized objects is a bad practice.
Consider refactoring your arrays:
struct Entity {
var offerProduct : String
var offerPrice : String
var savings : String
var shop : String
var imageName : String
var exDate : Int
var pickedID : String
var setDate : Int
}
var entities = [Entity]()
Then to sort single array of entities (once filled), you merely set necessary criteria, such as:
entities = entities.sorted(by: {$0.exDate > $1.exDate })
Make a structure:
struct Entry {
let product: String
let price: String
let saving: String
let shop: String
let imageName: String
let exDate: Int
let pickedID: String
let setDate: Int
}
Then use one array of these structures instead of several:
var entries = [Entry]()
You can sort this array easily by one of the structure's fields:
let expiryTimePoint = ...
entries = entries.sorted({
// compare absolute differences of intervals for sorting
return abs($0.exDate - expiryTimeInterval) > abs($1.exDate - expiryTimeInterval)
})

PFObject Array Sort

I'm using Parse and I have an array of PFObjects called "scorecardData". Each PFObject has a "score" property that is of type Int. I'm trying to sort my array by "score" but I'm getting the following error: "Binary operator '<' cannot be applied to two 'AnyObject?' operands". I'm not sure what I'm doing wrong here. I also tried down casting the objectForKey("score") as! Int but its not letting me do this. Any suggestions? Thanks in advance.
var scorecardData = [PFObject]()
scorecardData.sortInPlace({$0.objectForKey("score") < $1.objectForKey("score")})
You declared scorecardData variable as Array of PFObject. Why are you trying access PFObject property using objectForKey: reserved? Anyway I am not parse expert. But if you declared your array as [PFObject] you can use:
scorecardData.sortInPlace({$0.score < $1.score})
But this won't work unless you subclass PFObject for a more native object-oriented class structure. If you do that remember also to specify:
var scorecardData = [YOUR_NEW_CLASS]()
I strongly recommend subclassing PFObject to make use of all swift type-safe goodies.
But if you want to keep your data structure you can use:
scorecardData.sortInPlace({($0["score"] as! Int) < ($1["score"] as! Int)})
Keep in mind that it's dangerous, and in future avoid it.
If you want to Sort your array of PFOject... You can do this
extension Array where Element:PFObject {
func sort() -> [PFObject] {
return sort { (first, second) -> Bool in
let firstDate = first.objectForKey("time") as! NSDate//objectForKey(Constants.Parse.Fields.User.fullName) as? String
let secondDate = second.objectForKey("time") as! NSDate//objectForKey(Constants.Parse.Fields.User.fullName) as? String
return firstDate.compare(secondDate) == .OrderedAscending
}
}
}
Have you tried doing this?
var query = PFQuery(className:"ScoreCard")
// Sorts the results in ascending order by the score field
query.orderByDescending("score")
query.findObjectsInBackgroundWithBlock {

Converting Swift Array to NSData for NSUserDefaults.StandardUserDefaults persistent storage

I'm trying to get my head around Swift (after being relatively competent with Obj-C) by making a small app. I would like to use NSUserDefaults to persistently save a small amount of data but I am having problems.
I initialise an empty array of tuples like this:
var costCategoryArray: [(name:String, defaultValue:Int, thisMonthsEstimate:Int, sumOfThisMonthsActuals:Int, riskFactor:Float, monthlyAverage:Float)]=[]
When the array has an entry, I want to save the array to NSUserDefaults with standard Swift code such as this:
NSUserDefaults.standardUserDefaults().setObject(costCategoryArray, forKey: "financialData")
NSUserDefaults.standardUserDefaults().synchronize()
I get an error saying that the tuple array doesn't conform to the AnyObject class. So I tried to turn it into NSData:
var myNSData: NSData = NSKeyedArchiver.archivedDataWithRootObject(costCategoryArray)
var myUnarchivedData: Array = NSKeyedUnarchiver.unarchiveObjectWithData(myNSData)
...but I get the same error during the conversion to NSData. The object being held by my array doesn't conform to AnyObject. I've also tried at each stage to make it immutable by using:
let immutableArray = costCategoryArray
Ive also tried creating a class instead of using tuples which I understood would make it comply with AnyObject:
class costCategory : NSObject {
var name : String
var defaultValue : Int
var thisMonthsEstimate : Int
var sumOfThisMonthsActuals : Int
var riskFactor : Float
var monthlyAverage : Float
init (name:String, defaultValue:Int, thisMonthsEstimate:Int, sumOfThisMonthsActuals:Int, riskFactor:Float, monthlyAverage:Float) {
self.name = name
self.defaultValue = defaultValue
self.thisMonthsEstimate = thisMonthsEstimate
self.sumOfThisMonthsActuals = sumOfThisMonthsActuals
self.riskFactor = riskFactor
self.monthlyAverage = monthlyAverage
}
}
But the new error is:
"Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')"
What is the problem with an array of tuples? Why can't I store an array of class objects? I feel like I need some expert advice as so far everything I try to do with Swift is pretty much incompatible...
Thanks!
Anything you are archiving to NSData and back needs to implement the NSCoding protocol. I found that in addition, my Swift class had to extend NSObject. Here is a quick example of a Swift class that encodes and decodes:
class B : NSObject, NSCoding {
var str : String = "test"
required init(coder aDecoder: NSCoder) {
str = aDecoder.decodeObjectForKey("str") as String
}
override init() {
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(str, forKey: "str")
}
}
// create an Object of Class B
var b : B = B()
// Archive it to NSData
var data : NSData = NSKeyedArchiver.archivedDataWithRootObject(b)
// Create a new object of Class B from the data
var b2 : B = NSKeyedUnarchiver.unarchiveObjectWithData(data) as B
value of "financialData" should be in quotes:
NSUserDefaults.standardUserDefaults().setObject("costCategoryArray", forKey: "financialData")
NSUserDefaults.standardUserDefaults().synchronize()

Resources