How to get unique array in Swift3.0 - arrays

Scene, I've an array of NSObject class named Comment like var comments:[Comment]?. This class has property commentBywhich is also a NSObject class named User and it has property to identify unique user which is var key:String.above description as code,
class Comment:NSObject {
var commentBy:User?
}
class User:NSObject {
var key:String?
}
//In some other class
class someClass {
var comments:[Comment]?
}
What I want
I want an unique array of Comment, suppose that userA has made 2 comments and userB has made 1 comment so there is three comments total, but in my unique array, I want to show that two users made comments.
what should I do?

var comments: [Comment]?
let userKeys = comments?.flatMap { $0.commentBy?.key } ?? []
let set = Set(userKeys)
If all you want is the total number of unique user that have commented, then I think mapping comments to user's key then create a set of key is faster.

See what i do, actually your question is How to init a NSSet in swift ?
class Comment:NSObject {
var commentBy:User?
}
class User:NSObject {
var key:String?
}
//In some other class
class someClass {
var comments:[Comment]?
}
let user1 = User()
user1.key = "1"
let user2 = User()
user1.key = "2"
let c1 = Comment()
c1.commentBy = user1
let c2 = Comment()
c2.commentBy = user1
let c3 = Comment()
c3.commentBy = user2
let set: NSSet = [user1, user2]
let set2: NSSet = [user2]
set2.isSubset(of: set as! Set<AnyHashable>) //true
let set3: NSSet = [c1, c1]
set3.count //1
let set4: NSSet = [c1, c2, c3] // your unique array
let set5 = NSSet(array: [c1, c2, c1, c3])
set5.count //3
set3 will always be [c1], because the elements in Set or NSSet will be unique.

First make User hashable in a way that makes them equal based on their unique key.
class User: NSObject
{
let key:String
init(key: String) { self.key = key }
override func isEqual(_ object: Any?) -> Bool
{
guard let other = object as? User else { return false }
return self.key == other.key
}
override var hashValue: Int { return key.hashValue }
}
let eq = User(key: "foo") == User(key: "foo") // true
let h1 = User(key: "foo").hashValue // some big number
let h2 = User(key: "foo").hashValue // same big number as above
I've made key immutable because, as it serves to uniquely identify a user, it should not be possible to change.
Because User is an NSObject it already has an implementation of == which uses isEqual: so you override that to compare based on the key. You also use the hashValue of key as the User hash value.
Now you can use User in sets and as keys in dictionaries. If you do the same for Comment so that they compare equal on User you can use comments in a set too. However, that is a bad idea. Two comments are not the same just because they have the same user. You should change SomeClass to use a dictionary keyed by user as follows:
class Comment: NSObject
{
let user: User
let text: String
init(user: User, text: String)
{
self.user = user
self.text = text
}
}
class SomeClass
{
var userComments: [User : [Comment]] = [:]
func addComment(newComment: Comment)
{
if let existingComments = userComments[newComment.user]
{
userComments[newComment.user] = existingComments + [newComment]
}
else
{
userComments[newComment.user] = [newComment]
}
}
var userCount: Int { return userComments.count }
var commentCount: Int { return userComments.values.reduce(0, { $0 + $1.count}) }
}
The property userCount is the number you asked for. The property commentCount is the total number of comments - although beware: the implementation runs in O(n) time. If speed is of the essence, you should maintain a counter that increments each time you add a comment.

Related

Weekly activity summary help? SWIFTUI

I'm tryin' to obtain a list of activities ("dd/mm/YY: goal achieved/missed goal") which has to be setted every week. The problem is that I obtain a list of activities with the same date and the same result of the previous one. For example:
28/02/2022: goal achieved
28/02/2022: goal achieved
28/02/2022: goal achieved
and the next day:
01/03/2022: missed goal
01/03/2022: missed goal
01/03/2022: missed goal
01/03/2022: missed goal
I want to obtain, instead, a list like:
28/02/2022: goal achieved
01/03/2022: missed goal
02/03/2022: goal achieved...
These are useful structs:
struct Persistent {
#AppStorage("goalAchieved") static var goalAchieved : Bool = false
#AppStorage("activityList") static var activityList : [String] = []
}
struct obj {
static var currentDate = Date()
static var stringDate = ""
static var activity = Activity(date:Persistent.lastUpdatedDate)
}
This is the ActivityListView:
import SwiftUI
func activitystring(activity:Activity) -> String{
var output = ""
output = "\(activity.date): \(activity.reachedobj(goalAchieved: Persistent.goalAchieved))"
return output
}
struct Activity: Identifiable{
let id = UUID()
let date: String
func reachedobj(goalAchieved: Bool) -> String {
var output = ""
if Persistent.goalAchieved == false { output = "Missed goal" }
if Persistent.goalAchieved == true { output = "Goal Achieved!"}
return output
}
}
struct ActivityRow: View{
var activity: Activity
var body: some View{
Text(activitystring(activity: activity))
Divider()
}
}
struct ActivityListView: View {
var body: some View {
ScrollView{
Text("Week summary").font(.system(size: 15)).foregroundColor(Color.green)
Text("")
ForEach(Persistent.activityList, id: \.self) { activity in
let activity = Activity(date: Persistent.lastUpdatedDate)
ActivityRow(activity: activity)
}
}
}
}
Finally this is the useful code in the ApplicationApp file (main) where I update activity list:
MenuView().onAppear(){
if Persistent.activityList.count>7{
Persistent.activityList.removeAll()
}
obj.currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/YY"
obj.stringDate = formatter.string(from:obj.currentDate)
if Persistent.lastUpdatedDate != obj.stringDate{
Persistent.goalAchieved = false
let activity = Activity(date: Persistent.lastUpdatedDate)
Persistent.activityList.append(activitystring(activity: activity))
Persistent.lastUpdatedDate = obj.stringDate
}
}
What's wrong on this?
You're calling obj.activity in your ForEach and ActivityRow, that's why it repeats that same static property all over the place.
You better just drop your struct obj and try again without it
In your Persistent object you have an array of many activities, called activitylist , but one single boolean that tells if the goal is achieved - goalachieved indeed.
Your view is iterating through the array of Persistent.activitylist, so you will have many lines for one single result - achieved or not achieved. You might actually want to iterate over an array of Persistent objects - meaning that somewhere you should probably store [Persistent] in some variable. In this way, you will see one line only for each result.
If I also may suggest: use the conventions for naming variables, Swift uses "camelCaseConventionForVariables", easier to read than "thewholevariableislowercase"
Edit:
Let me try to change a little bit your code (I would personally change it more radically, but that's not the scope of the answer).
Instead of having only one goalAchieved for all elements on the array activityList, make it a dictionary:
struct Persistent {
// Drop this variable
// #AppStorage("goalAchieved") static var goalAchieved : Bool = false
// Make this a dictionary, the date will be the key and the goalAchieved will be the value
#AppStorage("activityList") static var activityList : [String: Bool] = [:]
}
Add values to the dictionary (#meomeomeo is right, you don't need obj):
MenuView().onAppear() {
if Persistent.activityList.count > 7 {
Persistent.activityList.removeAll()
}
let currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/YY"
let stringDate = formatter.string(from: currentDate)
if Persistent.lastUpdatedDate != stringDate {
let activity = Activity(date: Persistent.lastUpdatedDate)
Persistent.activityList[activitystring(activity: activity))] = false // Will create something like ["01/03/2022": false]
Persistent.lastUpdatedDate = stringDate
}
}
Iterate on the dictionary in your ForEach; for more info: read here.

deleting objects if string of object matches string in a separate array

I have an nsobject class with four strings
class Post: NSObject {
var author: String!
var postID: String!
var pathToImage: String!
var userID: String!
}
I also have a separate class viewcontroller which has a function grabbing posts from firebase. I have an array called posts = [Post](), which is filled by a seperate function going through firebase and grabbing data for each photo. I also have an array called removeArray which is array of strings, which the string is the postID of certain posts. Now this is my problem, I am trying to loop through removeArray, check if the each in removeArray = to the each in posts.postID and check if they are equal. Then either I delete that each in posts.postID post, or I create a new array that is posts - posts with postID's in removeArray. Here is my code now that does not work, it just keeps posts as is.
if posts != nil {
if var array = UserDefaults.standard.object(forKey: "removeArray") as? [String] {
for each in posts {
for one in array {
if one == each.postID {
new.append(each)
}
}
}
return self.posts.count
}
}
So if you have any idea how to take a string in an array, check if that string if eqaul to a string in an array of objects.postID, and remove that object from the array if it is equal. I have tried to research a way to filter it, but so far nothing. Please give me some feedback. Thanks
My problem = http://imgur.com/a/m5CiY
var posts = [p1,p2,p3,p4,p5]
let array = ["aaa","bbb"]
var new:Array<Post> = []
for each in posts {
for one in array {
if one == each.postID {
new.append(each)
}
}
}
print("This objects should be remvoed: \(new)")
posts = Array(Set(posts).subtracting(new))
print("After removing matching objects: \(posts)")
You could use reduce(_:_:)!
class Country {
var name: String!
init(name: String) {
self.name = name
}
}
let countries = [Country(name: "Norway"), Country(name: "Sweden"), Country(name: "Denmark"), Country(name: "Finland"), Country(name: "Iceland")]
let scandinavianCountries = ["Norway", "Sweden", "Denmark"]
// Store the objects you are removing here
var nonScandinavianCountries: [Country]?
let scandinavia = countries.reduce([Country](), {
result, country in
// Assign result to a temporary variable since result is immutable
var temp = result
// This if condition works as a filter between the countries array and the result of the reduce function.
if scandinavianCountries.contains(country.name) {
temp.append(country)
} else {
if nonScandinavianCountries == nil {
// We've reached a point where we need to allocate memory for the nonScandinavianContries array. Instantiate it before we append to it!
nonScandinavianCountries = []
}
nonScandinavianCountries!.append(country)
}
return temp
})
scandinavia.count // 3
nonScandinavianCountries?.count // 2
Resouces:
https://developer.apple.com/reference/swift/array/2298686-reduce

Is it a mistake of the compiler or I misunderstand something? [duplicate]

I noticed a bit weird (and dangerous IMHO) behavoir in Creating an Array with a Default Value. As stated in Swift 2.1: Collection Types
Swift’s Array type also provides an initializer for creating an array of a certain size with all of its values set to the same default value. You pass this initializer the number of items to be added to the new array (called count) and a default value of the appropriate type (called repeatedValue):
The point is: same default value; in order to understand how it work, I tried to create an array of elements of this example class
class User {
private struct Shared {
static var sequence: Int = 0
}
var id: Int
var thinkTime: NSTimeInterval // typealias di Double
init (thinkTime: NSTimeInterval) {
User.Shared.sequence = User.Shared.sequence+1
id = User.Shared.sequence
self.thinkTime = thinkTime
}
}
and this testing code:
let howManyUsers: Int = 3
var users = [User](count: howManyUsers, repeatedValue:User(thinkTime: 10.0))
let u2: User = User(thinkTime: 10)
let u3: User = User(thinkTime: 10)
users.append(u2)
users.append(u3)
users[1].thinkTime = 20
users[3].thinkTime = 30
for u in users {
print("User id:\(u.id) thinktime:\(u.thinkTime)")
}
gives:
User id:1 thinktime:20.0
User id:1 thinktime:20.0
User id:1 thinktime:20.0
User id:2 thinktime:30.0
User id:3 thinktime:10.0
that definitively proof the initializer with the number of items to be added to the new array and a default value of the appropriate type are: the same object instance
Which is the way, as concise and smart as possible, to obtain a array of distinct object instances , instatiated with the same default value ( not the same instance but a number of instances initialized with the same default value ) ?
Classes are reference types, therefore – as you noticed – all array
elements in
var users = [User](count: howManyUsers, repeatedValue:User(thinkTime: 10.0))
reference the same object instance (which is created first and then
passed as an argument to the array initializer).
For a struct type you would get a different result.
A possible solution:
var users = (0 ..< howManyUsers).map { _ in User(thinkTime: 10.0) }
Here, a User instance is created for each of the array indices.
If you need that frequently then you could define an array init
method which takes an "autoclosure" parameter:
extension Array {
public init(count: Int, #autoclosure elementCreator: () -> Element) {
self = (0 ..< count).map { _ in elementCreator() }
}
}
var users = Array(count: howManyUsers, elementCreator: User(thinkTime: 10.0) )
Now the second argument User(thinkTime: 10.0) is wrapped by the
compiler into a closure, and the closure is executed for each
array index.
Update for Swift 3:
extension Array {
public init(count: Int, elementCreator: #autoclosure () -> Element) {
self = (0 ..< count).map { _ in elementCreator() }
}
}
Swift 5
extension MSRoom {
static var dummyDefaultRoom: MSRoom = {
let members = MSRoom.Member.dummyMembers(maxCount: 6)
let ownerUser = members.first!.user
var room = MSRoom(id: "98236482724", info: .init(name: "Ahmed's Room", description: "your default room", isPrivate: true), owner: ownerUser)
room.dateCreated = Date(timeIntervalSince1970: 1565222400)
room.currentMembers = members
return room
}()
}
let rooms = [MSRoom](repeating: MSRoom.dummyDefaultRoom, count: 10)

Filtering arrays for use with UISearchBar

I have a table view which displays a user's Name, Company Name and Photo (PFFile). Each tableView row I have has all of this information in it.
I am using UISearchBarDelegate and IB to implement a search function to filter by the user's Name. It is finding the correct user but I have not been able to also update the company photo.
How do I filter the other arrays? The items I need from the arrays will be at the same index as the ones taken from the user's Name array.
EDIT: I am trying a different data structure and am receiving array index out of range, updated code below:
var filterArray = [User]() //<-- globally declared
var userArray = [User]() //< Global
class User {
var name: String?
var company: String?
init (name: String?, company: String?) {
self.name = name
self.company = company
}
}
//In a class which populates the search arrays
for object in unwrappedSucceeded {
let username = object.valueForKey("username") as! String
let companyName = object.valueForKey("companyName") as! String
let user = User(name: username, company: companyName)
userArray.append(user)
}
//tableViewController
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
filterArray.removeAll(keepCapacity: false)
if searchText.characters.count != 0 {
isSearch = true
self.search(searchText)
} else {
isSearch = false
}
}
func search(text: String) -> Void {
filterArray = userArray.filter({$0.name == text})
}
//In cellForRowAtIndexPath
cell.usernameCell.text = filterArray[indexPath.row].name //ARRAY INDEX OUT OF RANGE
Like I said you strongly recommend to group each user's info into one big container, therefore we could use array of struct or class, then it comes easier to filter.
schematic for the container:
struct Container
{
var username:String?
var companyName:String?
var photo:UIImage?
}
your main array will be : var arrayofData = [Container]()
Now when you are query your objects from parse, inside of your query function
// after you called the findObjectsWithBackgroundBlock()
// let's assume you check for error and if the [PFObject] is empty or not
for one in objectsFromParse
{
let photoToget = one["Photo"] as! PFFile
// next step should be to get the image data right :)
{
// let's assume that is the block when get the image data right:)
// check your data and assign it to some UIImage
// then
let userRepresentation = Container() //<-- we are creating a single object representation for each user
let username = one["username"] as! String //<--data we got from Parse
let companyName = one["companyName"] as! String
let userImage = //the UIImage which contains the data
userRepresentation.username = username
userRepresentation.companyName = companyName
userRepresentation.photo = userImage
// then we append
arrayOfData.append(userRepresentation)
}
}
Now we have all data into our array, so let's filter by username and also I hope you configure your tableView so when you have data from filter or regular array.
var filterArray = [Container]() //<-- globally declared
func search(text: String) -> Void
{
filterArray = arrayOfData.filter(){ (Container) -> Bool in
let range = Container.name!.rangeOfString(text, options:NSStringCompareOptions.CaseInsensitiveSearch) return range != nil }
// then you are good to go
}
let arr1 = [10,20,40]
let e1 = arr1.enumerate()
let arr2 = ["a","b","c"]
let f1 = e1.filter { $0.element % 20 == 0 }
let f2 = arr2.enumerate().filter { j, _ in
f1.contains { i, _ in
i == j
}
}
print(f1.map{$0.element}, f2.map{$0.element})
// [20, 40] ["b", "c"]
now you have both arrays "filtered". the best, what you can do is redesigning your data model!

Swift 1.2 Filter an Array of Structs by keyword

I need some help filtering an array of Structs.
This is what I am doing currently, it filters the array but not correctly.
For example lets say I search for an item in the array with "Mid" I have one item that should be shown however the item shown starts with "Bad".
var array = breweries.filter() { $0.name?.lowercaseString.rangeOfString(searchController.searchBar.text.lowercaseString) != nil }
results = array
here is my Struct
struct Breweries {
let name: String?
let breweryId: String?
let distance: Double?
let largeIconURL: String?
let streetAddress: String?
let locality: String?
let region: String?
let phone: String?
let website: String?
init(brewDictionary: [String: AnyObject]) {
name = brewDictionary["brewery"]?["name"] as? String
breweryId = brewDictionary["breweryId"] as? String
distance = brewDictionary["distance"] as? Double
largeIconURL = brewDictionary["brewery"]?["images"]??.objectForKey("large") as? String
streetAddress = brewDictionary["streetAddress"] as? String
locality = brewDictionary["locality"] as? String
region = brewDictionary["region"] as? String
phone = brewDictionary["phone"] as? String
website = brewDictionary["website"] as? String
}
}
Please point in the right direction!
Note: I am using Swift 1.2
Update:
I thought a video would be of help to better explain what I am trying to do.
Demo Of issue
What I want is to find the filter the array so only the item with a similar name is shown.
Update 2: As it turns out I forgot to handle the case when my UISearchController was active.
Assuming your Struct name is Breweries and it has a name property, try this:
let array = breweries.filter() {
($0.name!.lowercaseString as NSString).containsString(searchController.searchBar.text.lowercaseString)
}
Your usage of filter is correct, but your closure seem to be complicated with no clear goal. I suggest you to write an extension (or possibly use what I am using):
extension String {
func contains(search: String, ignoreCase: Bool = false, ignoreDiacritic: Bool = false) -> Bool {
var options = NSStringCompareOptions.allZeros
if ignoreCase { options |= NSStringCompareOptions.CaseInsensitiveSearch }
if ignoreDiacritic { options |= NSStringCompareOptions.DiacriticInsensitiveSearch }
return self.rangeOfString(search, options: options) != nil
}
}
This way you can use closure like this to search:
breweries.filter() {
$0.name?.contains("x") // Precise search
$0.name?.contains("x", ignoreCase: true, ignoreDiacritics: true) // Ignores diacritics and lower / upper case
}
of course, you can use | or & to search for multiple parameters
breweries.filter() {
$0.name?.contains("x") || $0.streetAddress?.contains("x")
}
Hope it helps!
Here is an example from an investing app with struct:
import Foundation
public struct SNStock {
public let ticker:NSString
public let name:NSString
init(ticker:NSString, name:NSString) {
self.ticker = ticker
self.name = name
}
}
Search on Main Thread:
public func searchStocksByKeyword(keyword:String) -> [SNStock] {
let lowercaseKeyword = keyword.lowercaseString
var searchResults:[SNStock] = []
searchResults = stocks.filter({ (stock:SNStock) -> Bool in
return stock.ticker.lowercaseString.hasPrefix(lowercaseKeyword)
})
if (searchResults.count == 0) {
searchResults = stocks.filter({ (stock:SNStock) -> Bool in
return stock.name.lowercaseString.hasPrefix(lowercaseKeyword)
})
}
searchResults.sortInPlace {
($0.ticker as String) < ($1.ticker as String)
}
return searchResults;
}
Search on Background Thread:
public func searchStocksByKeyword(keyword:String, completion:(stocks:[SNStock])->()) {
let qualityOfServiceClass = QOS_CLASS_USER_INTERACTIVE
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
let stocks:[SNStock] = self.searchStocksByKeyword(keyword)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(stocks: stocks)
})
})
}

Resources