Swift Realm - How to COUNT all data from single column? - ios

Here is what my class looks like:
class Card : Object {
#objc dynamic var tags: String = ""
#objc dynamic var set_id: String = ""
}
I want to return number of tags from all Cards with forwarded set_id.
Here is the method:
func totalTags() -> String {
var tagCounter: Int = 0
let realm = try? Realm()
let totalCards = realm!.objects(Card.self).filter("set_id = '\(setId)'") //all Cards with selected set_id, set_id is global var.
for card in 0...totalCards.count {
//every 'card' has tags, but there there can me more tags,
//like : tags="one,twitter,world,Europe"...
//And I want to count all of them for every 'card'
let result = realm.objects(Card.self).filter() //How to filter?
tagCounter += //what? result.count or something?
}
return String(tagCounter)
}

I understand that tags: String contains comma separated elements and you want to find the number of elements.
You can do that by iterating over totalCards. For each card, split the tags into an array and count the number of elements.
for card in totalCards {
tagCounter += card.tags.components(separatedBy: ",").count
}
components(separatedBy:) documentation

I know this was already answered but I just want to share how to do it my own way.
let tagsCount = totalCards.map { $0.tags.components(separatedBy: ",") }.flatMap { $0 }.filter { !$0.isEmpty }.reduce(into: 0, { result, _ in
result += 1
})
Thanks. Happy coding :)

Related

Return a single String from a dictionary using uniqueKeysWithValues

Goal of the code:
To assign a struct dictionary with Strings as Keys and String Arrays as values to a variable and then pull one (can be at random) specific String key value in the String Array and return that one String element in the underlying String Array so that it can be used elsewhere (potentially assigned to a label.text)
Essentially (please reference code below), I want to access one value at random in myDictionary using a specific key ("keyOne"), and pull, let's say, "Value2" then return only the string "Value2" from the underlying String Array associated with "keyOne" using indexing.
Errors are in the code below.
The issue I'm thinking is that I haven't figured out how to turn my final var Testing = dict["keyOne"] into an Int compatible index... if it was an index, the code would pull an Int value and the corresponding String from the three Strings in the underlying value array (due to the three String values associated with "keyOne").
Also, variableView() just inherits the datasource from several other containers, but the var dataSource : Structure? is the main reference, so that is what I included.
Code so far:
let myDictionary = [Structure(name: "keyOne", text: ["Value1", "Value2", "Value3"]), Structure(name: "keyTwo", text: ["Value4", "Value5", "Value6"])]
lazy var dict = Dictionary(uniqueKeysWithValues: myDictionary.lazy.map { ($0.name, $0.text) })
struct Structure: Hashable {
var name: String
var text: [String]
init(name: String, text: [String]){
self.name = name
self.text = text
}
}
func variable(at index: Int) -> variableView {
let variable = variableView()
var Testing = dict["keyOne"]
variable.dataSource = Testing![index] <- Cannot assign value of type 'String' to type 'structure'
return variable
var dataSource : Structure? {
didSet {
label.text = "This is a test"
} else {
// n/a
}
}
Please note that the error message is above in the code for variable.dataSource = Testing![index].
I am also suspecting that my issue lies in the "looping" logic of how I am assigning a variable with a struct, to a datasource which references that same struct.
Any help is appreciated as I have been stuck on this for legitimately a week (I truly have exhausted every single StackOverflow answer/question pair I could find).
THANK YOU!
EDIT:
I found this documentation to assist me greatly with this, and I recommend anyone with a similar question as mine to reference this: https://swift.org/blog/dictionary-and-set-improvements/
Given the question and the discussion in the comments I would add a mutating func to the struct that removes and returns a random string
mutating func pullText() -> String? {
guard let index = text.indices.randomElement() else {
return nil
}
return text.remove(at: index)
}
Example
if let index = myDictionary.firstIndex(where: { $0.name == "keyOne" }),
let text = myDictionary[index].pullText() {
someLabel.text = text
}
Here is another example based on the code in the question
Assuming VariableView looks something like this
struct VariableView: View {
var dataSource : Structure?
var word: String?
var body: some View {
Text(word ?? "")
}
}
Then the func variable can be changed to
func variable() -> VariableView {
var variable = VariableView()
if let index = dict.firstIndex(where: { $0.name == "keyOne" }) {
variable.dataSource = dict[index]
variable.word = dict[index].pullText()
}
return variable
}

iterate through two custom arrays and set values if variables are equal Swift

I have an easy question that is also hard at the same time. I have two separate structs (this can also work for classes):
struct FBTweet {
var tweetId: Int? //set
var tweetText: String? //set
}
and
struct Status {
var statusId: Int? //set
var statusText: String? //no value
}
I have an array of both structs var fbTweetArray: [FBTweet] = [] and var statusArray: [Status] = []
I have set every variable in to a certain value in each index in fbTweetArray but I only set the .statusId variable in each index for statusArray. For every statusArray.statusId value in statusArray, there is only one fbTweetArray.tweetId that has the same exact Int value. I am trying to make is so that if these two variables are the same then I should set set
statusArray.statusText to whatever fbTweetarray.tweetText is. So for example only fbTweetArray[1].tweetid = 2346 and statusArray[4].statusId = 2346 have 2346 as their value. There for if fbTweetArray[1].tweetText = "hello friend" then statusArray[4].statusText needs to be set to "hello friend".
So far I have
func testWhat () {
var fbTweetArray: [FBTweet] = []
var statusArray: [Status] = []
for fbTweet in fbTweetArray {
for var status in statusArray {
if (status.statusId == fbTweet.tweetId ) {
status.statusText = fbTweet.tweetText
}
}
}
}
how do I set the for var status in the for loop back into the statusArray since it is now a var and is different than one of the indexes in var statusArray: [Status] = []
Basically, you need only one for/forEach loop to achieve what you want:
var fbTweetArray: [FBTweet] = [
FBTweet(tweetId: 1, tweetText: "1"),
FBTweet(tweetId: 2, tweetText: "2"),
FBTweet(tweetId: 3, tweetText: "3")
]
var statusArray: [Status] = [
Status(statusId: 2, statusText: nil),
Status(statusId: 1, statusText: nil),
Status(statusId: 3, statusText: nil)
]
fbTweetArray.forEach { tweet in
if let index = statusArray.index(where: { $0.statusId == tweet.tweetId }) {
statusArray[index].statusText = tweet.tweetText
}
}
print(statusArray.map { $0.statusText }) // [Optional("2"), Optional("1"), Optional("3")]
Note, that your ids in both structures can be nil. To handle this situation (if both id is nil - objects are not equal) you can write custom == func:
struct Status {
var statusId: Int? //set
var statusText: String? //no value
static func == (lhs: Status, rhs: FBTweet) -> Bool {
guard let lhsId = lhs.statusId, let rhsId = rhs.tweetId else { return false }
return lhsId == rhsId
}
}
...
// rewrite .index(where: ) in if condition
if let index = statusArray.index(where: { $0 == tweet }) { ... }
Also, there is some pro-tip. If you adopt your structs to Hashable protocol, you will be able to place FBTweets and Statuses into Set structure. The benefits of that:
If you instead store those objects in a set, you can theoretically
find any one of them in constant time (O(1)) — that is, a lookup on a
set with 10 elements takes the same amount of time as a lookup on a
set with 10,000.
You can find more in-depth info about it in a new great article by NSHipster.
Your question is interesting only if both the arrays are not ordered.
To find the element from fbTweet array, you can sort it and employ binary search.
Then enumerate status array and find the fbTweet object with the same identifier and modify the status object. It needs to be saved again in the array as structs get copied on write.
extension Array where Element == FBTweet {
func binarySearchFBTweetWith(_ id:Int) -> FBTweet? {
var range = 0..<self.count
while range.startIndex < range.endIndex {
let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2
guard let tweetId = self[midIndex].tweetId else {
continue
}
if tweetId == id {
return self[midIndex]
} else if tweetId < id {
range = midIndex+1..<range.endIndex
} else {
range = range.startIndex..<midIndex
}
}
return nil
}
}
fbTweetArray.sort{($0.tweetId ?? 0) < ($1.tweetId ?? 0)}
for (index, status) in statusArray.enumerated() {
guard let statusId = status.statusId else {continue}
guard let fbTweet = fbTweetArray.binarySearchFBTweetWith(statusId) else {continue}
var status = status
status.statusText = fbTweet.tweetText
statusArray[index] = status
}
An alternative would be use dictionaries instead of arrays, for better performance and easier implementation (in this case). You can easily get the array of keys and values later If you need.
In this case, the Id would be the Key of the dictionary, and the text the value.

Swift: Merging 2 or more elements custom objects in array based on unique value

Below is my custom object class.
class UserGroups: NSObject {
let groupName: String
let users: [CheckIn]?
init(json:JSON) {
self.groupName = json[Constants.Models.UserGroups.groupName].stringValue
self.users = UserGroups.getUserGroupsList(jsonArray: json[Constants.Models.UserGroups.users].arrayValue)
}
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn]{
return jsonArray.flatMap({ (jsonItem: JSON) -> CheckIn in
return CheckIn(json: jsonItem)
})
}
}
I've an array of above custom objects. How can I combine 2 or more custom objects into a single object by merging users of every object having same groupName.
Below is my CheckIn Model:
class CheckIn: NSObject {
let id: String
let firstName: String
let lastName: String
let latitude: String
let longitude: String
let hint: String
init(json: JSON) {
self.id = json[Constants.Models.CheckIn.id].stringValue
self.firstName = json[Constants.Models.CheckIn.firstName].stringValue
self.lastName = json[Constants.Models.CheckIn.lastName].stringValue
self.hint = json[Constants.Models.CheckIn.hint].stringValue
self.latitude = json["location"][Constants.Models.CheckIn.latitude].stringValue
self.longitude = json["location"][Constants.Models.CheckIn.longitude].stringValue
}
}
id field is not unique in CheckIn.
Here's a slightly simplified example that shows how to combine groups that have the same group name.
Here is the UserGroup class. users is now a variable (var) because we will be adding elements to groups to combine them.
class UserGroups: NSObject {
let groupName: String
var users: [String]?
init(groupName: String, users: [String]?) {
self.groupName = groupName
self.users = users
}
}
Here are three groups, two of the share the same group name, Blues.
let group1 = UserGroups(groupName: "Blues", users: ["Tom", "Huck", "Jim"])
let group2 = UserGroups(groupName: "Reds", users: ["Jo", "Ben", "Tommy"])
let group3 = UserGroups(groupName: "Blues", users: ["Polly", "Watson", "Douglas"])
Next, we'll put all the groups in an array.
let allGroups = [group1, group2, group3]
Here, we use Swift's reduce function to allow us to reduce the array to only groups with unique group names.
let compacted = allGroups.reduce([UserGroups](), { partialResult, group in
var dupe = partialResult.filter {$0.groupName == group.groupName }.first
if let dupeGroup = dupe {
dupeGroup.users?.append(contentsOf: group.users ?? [])
return partialResult
} else {
var newPartialResult = partialResult
newPartialResult.append(group)
return newPartialResult
}
})
The array is now reduced to unique groups, we print out all the groups and their users with the help of Swift's map function.
print(compacted.map { $0.users })
// Prints [
Optional(["Tom", "Huck", "Jim", "Polly", "Watson", "Douglas"]),
Optional(["Jo", "Ben", "Tommy"])
]
The Solution
You did not include the CheckIn model, but I will assume that it has some sort of an id field unique to each user. We will use this to make the object Hashable:
// Add this to your file outside of the UserGroups class
extension CheckIn: Hashable {
var hashValue: Int { return self.id }
}
Making it Hashable allows you to convert the Array to a Set, which does not allow duplicates and will remove them in a very efficient way.
// Change getUserGroupsList as follows
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn] {
return Array(Set(jsonArray.flatMap({ (jsonItem: JSON) -> CheckIn in
return CheckIn(json: jsonItem)
})))
}
Optional Considerations
As an aside, in case you're coming from another language, Swift gives you nice type inference and default names for closure arguments ($0 is the first argument). You can probably make the code a little less verbose, but it's a matter of taste which is preferred.
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn] {
return Array(Set(jsonArray.flatMap { CheckIn(json: $0) }))
}
Also consider whether you really want the return value to be an array. If you want the list to always have unique users, it is a bit more efficient to use a Set as your return type and forgo the conversion back to an Array like this:
class func getUserGroupsList(jsonArray: [JSON]) -> Set<CheckIn> {
return Set(jsonArray.flatMap { CheckIn(json: $0) })
}
Finally, consider whether you really need the users property to be optional. With sequence types, it is often sufficient to use an empty sequence to denote absence of a value. Depending on your situation, this may simplify your code. The final version looks like this:
class UserGroups: NSObject {
let groupName: String
let users: Set<CheckIn>
init(json:JSON) {
self.groupName = json[Constants.Models.UserGroups.groupName].stringValue
self.users = UserGroups.getUserGroupsList(jsonArray: json[Constants.Models.UserGroups.users].arrayValue)
}
class func getUserGroupsList(jsonArray: [JSON]) -> Set<CheckIn> {
return Set(jsonArray.flatMap { CheckIn(json: $0) })
}
}
Maintaining Order
The caveat is that Set does not maintain the order of the items. If the order of the groups does matter, we can use this solution instead:
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn] {
var encountered: Set<CheckIn> = []
return jsonArray.flatMap { CheckIn(json: $0) }.filter { encountered.update(with: $0) == nil }
}
In this version, we still use a set, but only to maintain a set of items we've encountered already. The update method on a set returns the same value if it's already in the set or returns nil if it's being inserted for the first time. We use that to filter our array to those items being encountered for the first time while adding them to the set of encountered items to filter them out when they are subsequently encountered again.

How to get the unique id's of objects in an array swift

I have a custom class like this -
class Event: NSObject
{
var eventID: String?
var name:String?
}
Now i have an array of Event object's like
var events = [Event]()
var event1 = Event()
event1.eventID = "1"
event1.name = "Anu"
var event2 = Event()
event2.eventID = "2"
event2.name = "dev"
var event3 = Event()
event3.eventID = "1"
event3.name = "Anu"
events.append(event1)
events.append(event2)
events.append(event3)
to get the unque eventID's from array i have written code like this which is working great -
func getUniqueIDsFromArrayOfObjects(events:[Event])->NSArray
{
let arr = events.map { $0.eventID!}
let uniquearr:NSMutableArray = NSMutableArray()
for obj in arr
{
if !uniquearr.containsObject(obj) {
uniquearr.addObject(obj)
}
}
return uniquearr;
}
print(getUniqueIDsFromArrayOfObjects(events))
I wanted to know is there any alternate way to get the unique id's from array of objects more effectively than i am using . May be something by using NSPredicate.
Because an array having thousands of objects, my code going to do more iteration .
You can use a Set to obtain only the unique values. I would suggest that you have your function return a Swift array rather than NSArray too.
func getUniqueIDsFromArrayOfObjects(events:[Event])->[String]
{
let eventIds = events.map { $0.eventID!}
let idset = Set(eventIds)
return Array(idset)
}
let uniqueRecords = jobs.reduce([], {
$0.contains($1) ? $0 : $0 + [$1]
})
A Set is a collection similar to an array, which prevents duplicates. You can do:
func getUniqueIDsFromArrayOfObjects(events:[Event])->[Event] {
return Array(Set(events.map { $0.eventID! }))
}
Note that the order of the items in a set is undefined, so if you care about the order of the elements, you should try a different solution.

Construct typed dictionary using swift

I would like to create a typed map (Dictionary) class to meet the following requirements:
func testMap() {
var map = ActivitiesMap()
var activity = Activity()
activity.title = "Activity 1"
activity.uuid = "asdf1234"
map[activity.uuid] = activity
for (key, mapActivity) in map {
logger.debug("ACTIVITY MAP: \(key)=\(mapActivity)")
}
}
In short, I want this class to both be a dictionary such that it can be used in the for loop, however I want to ensure the keys are strings and the values are Activity objects.
I tried many variations of inheriting from Dictionary or typing the class, but so far it's resulted in multiple errors.
EDIT:
I don't think a simple generic dictionary will work, such as String:Activity. I want to have extra methods in the ActivityMap class, such as getAllActivitiesBetweenDates().
I need an actual class definition, not a generic dictionary expression.
You can make it looks like dictionary by implement subscript operator
And conform to Sequence protocol to support for-in loop
struct ActivitiesMap : Sequence {
var map = [String:Activity]()
subscript(key: String) -> Activity? {
get {
return map[key]
}
set(newValue) {
map[key] = newValue
}
}
func generate() -> GeneratorOf<(String, Activity)> {
var gen = map.generate()
return GeneratorOf() {
return gen.next()
}
}
// I can't find out type of map.generator() now, if you know it, you can do
//func generate() -> /*type of map.generator()*/ {
// return map.generate();
//}
}
This works for me. Not sure what is in your ActivitiesMap class, but just typed a Dictionary
class Activity{
var title:String = "";
var uuid: String = "";
}
func testMap() {
//var map = ActivitiesMap()
var map: Dictionary< String, Activity> = Dictionary< String, Activity>();
var activity = Activity()
activity.title = "Activity 1"
activity.uuid = "asdf1234"
map[activity.uuid] = activity
for (key, mapActivity) in map {
println("ACTIVITY MAP: \(key)=\(mapActivity)")
}
}
testMap();
This is my output:
ACTIVITY MAP: asdf1234=C11lldb_expr_08Activity (has 2 children)
class Activity {
var title=""
var id=""
init(id:String, title:String) { self.id=id; self.title = title }
}
var activities = [String:Activity]()
let a1 = Activity(id:"a1", title:"title1")
let a2 = Activity(id:"a2", title:"title2")
let a3 = Activity(id:"a3", title:"title3")
activities[a1.id] = a1
activities[a2.id] = a2
activities[a3.id] = a3
for (id,activity) in activities {
println("id: \(id) - \(activity.title)")
}
should print
id: a2 - title2
id: a3 - title3
id: a1 - title1
(key order not guaranteed to be the same)
You can use typealias keyword to define nice name of any type.
Here is how it can be used for your code:
class Activity { /* your code */ }
typealias ActivityMap = Dictionary<String, Activity>
var activityDict = ActivityMap()
And to support custom functions you can write an extension, example bellow:
extension Dictionary {
func getAllActivitiesBetweenDates(fromDate:NSDate, toDate:NSDate) -> Array<Activity>
// your code
return []
}
}
Usage:
let matchedActivities = activityDict.getAllActivitiesBetweenDates(/*date*/, /*date*/)

Resources