I have two arrays of users and beams
struct User {
var id: Int
var jobTitle: String
}
struct Beams {
var isSelected = false
var id: Int
var userIds: [Int]
}
If the user selects a particular beam, all the users which are assigned to that beam will be selected of the first array. The user can select multiple beams and i am getting the data from api response, and i have to send the selected users in a post call. so i have to select all the selected users from an array of beams and on the basis of filtered ids, send that array in a post call. initially i am filtering all the beams which are selected like this
beams.filter { $0.isSelected }.compactMap { $0.userIDS }
which gives me an array of [Int]. These are the userIds which have to be sent. I somehow can't figure out how will i select these particular ids from my array of users which contains other attributes as well. contains can be used to filter one element but not a sequence. and even if i filter or take an intersection of these ids and my userArray. i am still left with a set or an array of ids. from which i'd have to generate my array, i want to keep all the other attributes as well. Any help in the right direction would be appreciated. I have something of this nature in my mind
let selectedBeamsIds = beams.filter { $0.isSelected }.compactMap { $0.userIDS }
users.append(selectedBeamsIds.compactMap { getUniqueUsersForBeams(ids: $0) } )
private func getUniqueUsersForBeams(ids: [Int]) -> [User] {
let ower = users.reduce(into: [User]()) { (result,
user) in
let filteredUserIds = ids.filter { $0 == user.id }
//result.append(use)
}
}
I also need to do this with another array of jobTitles which is like
struct JobTitle {
var jobTitle: String
var isSelected = false
}
this one is straight forward as if the users's jobTitle matches with the selected items of [String] array then select that user to be sent.
Try,
let selectedIds = beams.filter { (beams) -> Bool in
beams.isSelected
}.flatMap { (beams) -> [Int] in
beams.userIds
}
let filteredUsers = users.filter { (user) -> Bool in
selectedIds.contains(user.id)
}
1 ) Filter out selected beams
2 ) Collect all the userIds in the selected beams
3 ) Filter users by checking if the id is present in the previously collected ids.
Related
struct User{
var firstName:String
var lastName:String
var city:String
var email:String
}
var users = [User]
I am trying to filter users as someone is typing in the textfield. Its like Search for the email. It should show all the matching results but shouldn’t duplicate the same user. I am able to filter the array based on one property such as name but not sure how to filter the array based on all the properties.
I’ve implemented the UITextField delegate and have this code for filtering.
let filteredArray = users.filter({ (user) -> Bool in
return user.firstName.lowercased().contains(“John”)
})
let filteredArray = users.filter({ $0.firstName.lowercased().contains("firstName") || $0.lastName.lowercased().contains("lastName") || ... })
You can set multiple conditions and combine them together with OR (||) or AND (&&)- its a simple boolean, you can think of it as it was in an if statement-
if user.firstName.lowercased().contains("john") || user.lastName.lowerCased().contains("lastname") { return true }
else { return false }
so in your code it will be like
let filteredArray = users.filter { (user) -> Bool in
return user.firstName.lowercased().contains("john") || user.lastName.lowercased().contains("lastname") }
Since you'll probably need to search multiple things, I would recommend you make a Searchable protocol, and make aggregate types searchable by virtue of checking if any of their constituents match.
import Foundation
struct User {
let firstName: String
let lastName: String
let city: String
let email: String
let age: Int // an example of a non-String type.
}
protocol Searchable {
func matches(query: String) -> Bool
}
extension String: Searchable {
func matches(query: String) -> Bool {
// Implement any kind of searching algorithm here. Could be as smart as fuzzy seraching
// or as basic as this case-insenitive simple substring search
return self.lowercased().contains(query)
}
}
extension Int: Searchable {
func matches(query: String) -> Bool {
return String(self).matches(query: query)
}
}
extension User: Searchable {
func matches(query: String) -> Bool {
let constituents: [Searchable] = [firstName, lastName, city, email, age]
return constituents.contains(where: { $0.matches(query: query) })
}
}
My code stays in the second for forever, testing the same category every step and decrementing every time.
I have two arrays, one of them is called categoriesToUpdate and is a list of category ids (string values) for categories that I have to update, and the other is called categories, containing all the actual category data I'm working with.
I have to test if the id value for a category that I have to update is the same as the database and if it is, decrement the attribute position of its object and update the database. But it is infinitely decrementing the bank.
let newCategory;
let name;
let position;
for(let id of categoriesToUpdate) {
for(let cat of categories) {
if(id === cat.id) {
position = cat.category.category.lastPosition - 1;
name = cat.category.category.categoryName;
newCategory = {
category: {
categoryName: name,
lastPosition: position,
}
}
cRef.child(id).update(newCategory);
}
}
}
Examples of the two arrays:
categoriesToUpdate = ['-lGz4j77...', '-uEbKO3...', ...]
and
categories = [
{
category: {
category: {
categoryName: "name",
lastPosition: "number",
}
},
id: "category id";
},
{
...
}
]
it is difficult to explain how I get the arrays, but basically, categoriesToUpdate is an array of ids that I add to my logic, I have to do update in each of these categoriesand categories is an array that has all categories of the database, comes from Firebase.
let id of categoriesToUpdate. I'm assuming categoriesToUpdate is an array of Category objects. So id is actually a Category object. To compare it should be id.id === cat.id.
Also you can try filter instead of a second loop.
Something like
var matched = categories.filter(c => c.id === id.id)[0];
Then compare matched. Nested loops are hard to read, imo.
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
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.
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!