Swift: Finding duplicate CNContact objects in an array - ios

I am trying to get duplicate CNContacts inside an Array:
Code:
func fetchDuplicateContacts(){
self.didFinished_getDuplicateContacts = false;
self.arr_duplicateContacts.removeAllObjects()
NSLog("Start fetch duplicate", "")
self.duplicateContactsCount = 0;
for (var i: Int = 0; i < self.arr_allContacts.count; i++){
let contact1 = self.arr_allContacts[i]
var hasDuplicate: Bool = false
let arr_childs: NSMutableArray = NSMutableArray()
for (var j: Int = 0; j < self.arr_allContacts.count; j++){
let contact2 = self.arr_allContacts[j]
if(contact1 != contact2){
if CNContactFormatter.stringFromContact(contact1, style: .FullName) == CNContactFormatter.stringFromContact(contact2, style: .FullName){
if(self.checkIfContactsHaveSamePhones(contact1, contact2: contact2)){
print("Move on cuz duplicate: \(CNContactFormatter.stringFromContact(contact1, style: .FullName))")
duplicateContactsCount += 1;
arr_childs.addObject(NSDictionary(objects: [contact2, false], forKeys: ["object", "checked"]))
hasDuplicate = true
}
}
}
}
// This is for adding first contact to main array to be the first. It's important to be like this
if hasDuplicate == true{
arr_childs.insertObject(NSDictionary(objects: [contact1, false], forKeys: ["object", "checked"]), atIndex: 0)
var key: NSString? = CNContactFormatter.stringFromContact(contact1, style: .FullName)
if key == nil {
key = "no name \(i)"
}
arr_duplicateContacts.addObject([key! : arr_childs])
}
}
NSLog("End fetch duplicate w results \(self.duplicateContactsCount)", "")
self.didFinished_getDuplicateContacts = true;
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.mytable.reloadData()
})
}
I loop trough the array, check each 2 contacts if have same name and same numbers and if true, add them to an Array of NSDictionary (whose key is contact name, and object is an NSDictionary which contain CNContact and a bool "checked").. A little bit messy, I know.
*The problem: I will get duplicates inside main array *
Let's say that I have Contact1["bob", "07100"], Contact2["bob","07100"]. When "j" loop will check if Contact1 == Contact1 which is true and skips adding object to array and after that checks Contact1 == Contact2(false and then it sees that Contact2 is a duplicate and ads to main array). After i++ it does same thing with Contact2 and this is the problem (try figure out for 3 objects if is not clear).
Tried solving it by asuming that duplicate contacts are one after another and used that i = j but if Contact1 == Contact3, Contact2 will be skiped from verification
Any idea how to solve this problem?

I thought I would propose another way of achieving what you are trying to do leveraging some of Swift's built in functions. One thing to note about this solution is that it will not help you determine which is the duplicate and which is the contact the user wants to keep. This is actually an issue with your approach because what happens if both contacts have the same name but a different number? This is why in my solution I group the duplicates and you (or your user) can decide what to do.
Get an array of the all the full names:
let fullNames = self.arr_allContacts.map(CNContactFormatter.stringFromContact($0, style: .FullName))
Make that array unique
let uniqueArray = Array(Set(fullNames))
This is the step that I will be doing differently than what you are doing. I will build an array of arrays because I think it will get to where you want to go.
var contactGroupedByUnique = [Array]()
for (fullName in uniqueArray) {
var group = self.arr_allContacts.filter {
CNContactFormatter.stringFromContact($0, style: .FullName) == fullName
}
contactGroupedByUnique.append(group)
}
Now you can do the following:
contactGroupedByUnique.count = //number of unique contact
contactGroupedByUnique[index] = //number of duplicates of that contact
I don't have time to test the code but this should get you there.

Related

Swift iOS -How to sort array of individual objects into separated arrays based on similar property

I have an array of Super Hero objects. I want to group the superheroes based on the name property into separated arrays and then count how many objects are in each individual separated array
Object:
class SuperHero{
var name: String?
var power: Bool?
}
Array of superheroes (there can be an infinite num of superheroes)
var superHeroes = [SuperHero]()
let superHero1 = SuperHero()
superHero1.name = "SuperMan"
superHero1.power = true
superHeroes.append(superHero1)
let superHero2 = SuperHero()
superHero2.name = "BatMan"
superHero2.power = true
superHeroes.append(superHero2)
let superHero3 = SuperHero()
superHero3.name = "BatMan"
superHero3.power = true
superHeroes.append(superHero3)
let superHero4 = SuperHero()
superHero4.name = "SuperMan"
superHero4.power = true
superHeroes.append(superHero4)
//etc...
Use name property to sort:
let sortedHeros = superHeroes.sort{$0.name < $1.name}
for hero in sortedHeros{
print(hero.name)
/*
prints
BatMan
BatMan
SuperMan
SuperMan
*/
}
How do I put the sorted array into separate arrays then print the count of each separated array?
//this is what I want
separatedArraysOfSuperHeroes = [[superHero2, superHero3], [superHero1, superHero4]]
//subscriprting isn't ideal because i'll never know the exact number of separated arrays
print(separatedArraysOfSuperHeroes[0].count)
print(separatedArraysOfSuperHeroes[1].count)
As per the comments the reason why I want sub arrays is because I want to use them to populate different tableview sections. For i.e. inside my tableview I would now have a 2 sections. The first section would have a header that says "Batman" with 2 Batman objects inside of it and the second section would have a header that says Superman with 2 Superman objects inside of it. The count property would show the number of super hero objects inside each section.
func getSeparatedArrayBasedOnName(superHeroes: [SuperHero]) -> [[SuperHero]] {
guard let superNames = NSOrderedSet.init(array: superHeroes.map { $0.name ?? "" }).array as? [String] else {
print("Something went wrong with conversion")
return [[SuperHero]]()
}
var filteredArray = [[SuperHero]]()
for superName in superNames {
let innerArray = superHeroes.filter({ return $0.name == superName })
filteredArray.append(innerArray)
}
for array in filteredArray {
for hero in array {
print(hero.name ?? "")
}
}
return filteredArray
}

Swift 3 For loop compare and change

Here is my code so far
var counter = 0
for i in 0...9 {
var val = NamePicker()
// array to find duplicates
var buttonValues = ["", "", "", "", "", "", "", "", "", ""] // array for button names
buttonValues.insert(val, at: counter)
print(buttonValues[counter])
counter += 1
}
This code is putting 10 string values into my array. What I would like to do is find a way to check each value in my array. for eample if my end result array is ["a","a","a","b","b","c","c","e","f","c"] I want to see if there is a triple of the same name(single and duplicates are fine). However if there is a triple I would like to change the 3rd value to another val from my NamePicker() function.
so with my array of
["a","a","a","b","b","c","c","e","f","c"]
there are 3 "a" and 3 "c", having two of the same is ok, i would like to change the 3rd to a new values and if the new value makes another triple it will change until there are no more triples.
so that array could possible have an end result of
["a","a","f","b","b","c","c","e","f","z"]
this is where the triples where changed.
Any help on how to do this efficiently?
Both options below asume that your NamePciker() function can generate at least 5 distinct values so there exists an array that satisfies your requirement.
Your requirement is better handled by not generating so many duplicates to begin with. If all you want is an array of names when each name cannot be repeated more than twice, try this:
var buttonValues = [String]()
var dict = [String: Int]()
while buttonValues.count < 10 {
let name = NamePicker()
let count = dict[name] ?? 0
guard count < 2 else { continue }
buttonValues.append(name)
dict[name] = count + 1
}
If you already have the array and want to correct it, do this:
var buttonValues = ["a","a","a","b","b","c","c","e","f","c"]
// Scan the array to tally how many times each name appears
var totalDict = [String: Int]()
buttonValues.forEach { totalDict[$0] = (totalDict[$0] ?? 0) + 1 }
// Now scan it again to update names that appear too many times
var runningDict = [String: Int]()
for (index, value) in buttonValues.enumerated() {
let count = runningDict[value] ?? 0
if count >= 2 {
while true {
let newValue = NamePicker()
let newTotal = (totalDict[newValue] ?? 0) + 1
if newTotal < 3 {
buttonValues[index] = newValue
totalDict[newValue] = newTotal
break
}
}
} else {
runningDict[value] = count + 1
}
}
Dictionary is the best way I think. Have the key be the character and the value be the count of that character. Your runtime will be O(n) since you only have to run through each input once. Here is an example:
let chars = ["a","a","a","b","b","c","c","e","f","c"]
var dict = [String: Int]()
for char in chars {
//If already in Dictionary, increase by one
if var count = dict[char] {
count += 1
dict[char] = count
} else {//else is not in the dictionary already, init with 1
dict[char] = 1
}
}
Output:
["b": 2, "e": 1, "a": 3, "f": 1, "c": 3]
Now I'm not sure how you want to replace the value that's the same character for a third time, but this is probably the best way to group the strings to determine which are over the limit.
Instead of inserting the wrong value and then checking if the values are correct, I would suggest to automatically create the correct array.
//array for button names
var buttonValues = Array<String>()
//tracks what value has been inserted how many times
var trackerDict = [String: Int]()
for i in 0...9 {
//we initialize a new variable that tells us if we found a valid value (if the value has not been inserted 2 times already)
var foundValidValue = false
while !foundValidValue{
var val = NamePicker()
//now we check if the value exists and if it is inserted less than 2 times
if let count = trackerDict[val] {
if count < 2 {
foundValidValue = true
}
}
//if we found the value, we can add it
if foundValidValue {
trackerDict[val] = (trackerDict[val] ?? 0) + 1
buttonValues.append(val)
}
//if we did not find it, we just run through the loop again
}
}
I added a dictionary because it is faster to keep track of the count in a dictionary than counting the number of occurrences in the array every time.

Does Swift have a concept of a unique unordered collection of values?

I have two collections of phone numbers that I want to compare to see if any match. In other languages, I'd loop through one collection, add it to a collection var type that requires uniqueness, loop through the other and check for matches such as:
var phones = ["1","2","3"]
var phones2 = ["2","5","6"]
var uniqueCollection: Set = Set()
for var i = 0; i < phones.count; i++ {
if (uniqueCollection.containsKey(phones[i]) == false){
uniqueCollection.add(phones[i])
}
}
var anyMatch = false
for var j = 0; j < phones2.count; j++{
if uniqueCollection.containsKey(phones2[j]) {
anyMatch = true
}
}
So far I haven't found any way to do this as Swift Maps seem to be a transform, Dictionaries require a value to go with the keys, and don't have an explicit "containsKey()" type function, and it doesn't seem there's another collection like "a hash table" with a method to see if a var is in there.
http://www.weheartswift.com/higher-order-functions-map-filter-reduce-and-more/
http://nshipster.com/swift-comparison-protocols/
Assuming this doesn't exist, I'm planning to just go the long way of double loops, which will suck for performance if there's ever two large collections.
func checkMultisAnyMatch(personMultis: [[AnyObject]], parseMultis: [[AnyObject]]) -> Bool{
//Put all the phone #'s or emails for the person into an Array
//Put all the phone #'s or emails for the PF contact into an array
//Loop through the phones in the parseContact
//if any match, break the loop, and set anyPhoneMatch = true
var anyMatch = false
for var i = 0; i < personMultis.count; i++ {
//order is Id, label, value, type
//that means it's in the 3rd column, or #2 subscript
var personMulti:AnyObject? = personMultis[i][2]
if (personMulti != nil) {
for var j = 0; j < parseMultis.count; j++ {
//order is Id, label, value, type
var parseMulti:AnyObject? = parseMultis[j][2]
if parseMulti != nil {
if parseMulti! as NSString == personMulti! as NSString {
anyMatch = true
}//if 4
}//if 3
}//for 2
}//if
}//for
return anyMatch
}
Would NSSet work for you?
func intersectsSet(_ otherSet: NSSet) -> Bool
Returns a Boolean value that indicates whether at least one object in the receiving set is also present in another given set.
You can create an NSSet from an NSArray.
var set1 = NSSet(array:["number1", "number2", "number3"])
var set2 = NSSet(array:["number4", "number2", "number5"])
var set3 = NSSet(array:["number4", "number5", "number6"])
let contains1 = set1.intersectsSet(set2) // true
let contains2 = set1.intersectsSet(set3) // false

Subsequent PFObject.saveAll() doesn't work in iOS Swift app

This method is called twice in a row, once for Emails, and again for Phone numbers, in an attempt to save that info to a separate object for each contact as I couldn't figure out how to save nested arrays to a single Parse object.
The baffling thing is that if I only do the method for emails, all save fine. If I only do it for Phone numbers, all save fine. When I do emails and then phones, only phones save. When I do phones and then emails, emails save.
The exception is that if a given contact only has an email or phone it always succeeds. Commenting out the pointer to Contacts didn't do anything to help, so it appears there's some race condition or locking error that's going on here with Parse.
Any ideas? I'd love to learn how to do nested arrays to parse too if that's possible, but I tried a few things and couldn't figure out how to get that to work.
func updateMultiField(person: ABRecord, parseObject: PFObject, fieldToGrab: ABPropertyID, contact: PFObject){
var multiArray:ABMultiValueRef = extractABMultiRef(ABRecordCopyValue(person, fieldToGrab))!
var parseObjects: [PFObject] = [PFObject]()
for (var j = 0; j < ABMultiValueGetCount(multiArray); ++j){
var multi = MultiRef()
var multiValueRaw = ABMultiValueCopyValueAtIndex(multiArray, j)
multi.value = extractMultiValue(multiValueRaw)
if (multi.value != nil) {
//get type
multi.type = getMultiType(fieldToGrab)
//get label
multi.label = extractMultiLabel(ABMultiValueCopyLabelAtIndex(multiArray, j))
//get id
multi.id = String(Int(ABMultiValueGetIdentifierAtIndex(multiArray, j)))
parseObject[parseContactIdFieldName] = contact
parseObject[labelFieldName] = multi.label
parseObject[valueFieldName] = multi.value
parseObject[multiIdFieldName] = multi.id
parseObject[typeFieldName] = multi.type
println("\(multi.type) multi about to be saved with value of \(multi.value)")
parseObjects.insert(parseObject, atIndex: j)
//save
}//if
}//for
PFObject.saveAll(parseObjects)
}//updateField
An earlier method calls this method twice:
updateMultiField(person, parseObject: multis, fieldToGrab: kABPersonPhoneProperty, contact: contact)
updateMultiField(person, parseObject: multis, fieldToGrab: kABPersonEmailProperty, contact: contact)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
UPDATE:
Here's the fixed code, which no longer passes a PFObject into the method and tries to save the same object over and over, but rather instantiates it in the for loop (5th line), which resolves the problem:
func updateMultiField(person: ABRecord, fieldToGrab: ABPropertyID, contact: PFObject){
var multiArray:ABMultiValueRef = extractABMultiRef(ABRecordCopyValue(person, fieldToGrab))!
var parseObjects: [PFObject] = [PFObject]()
for (var j = 0; j < ABMultiValueGetCount(multiArray); ++j){
var parseObject = PFObject(className: multisObjectName)
var multi = MultiRef()
var multiValueRaw = ABMultiValueCopyValueAtIndex(multiArray, j)
multi.value = extractMultiValue(multiValueRaw)
if (multi.value != nil) {
//get type
multi.type = getMultiType(fieldToGrab)
//get label
multi.label = extractMultiLabel(ABMultiValueCopyLabelAtIndex(multiArray, j))
//get id
multi.id = String(Int(ABMultiValueGetIdentifierAtIndex(multiArray, j)))
parseObject[parseContactIdFieldName] = contact
parseObject[labelFieldName] = multi.label
parseObject[valueFieldName] = multi.value
parseObject[multiIdFieldName] = multi.id
parseObject[typeFieldName] = multi.type
println("\(multi.type) multi about to be saved with value of \(multi.value)")
parseObjects.insert(parseObject, atIndex: j)
}//if
}//for
PFObject.saveAll(parseObjects)
}//updateMultiField
You seems trying to save the same parseObject 2 times without retrieving it in between. the last save will be always ignored since the object you are trying to save is outdated (a newest version is already on server).
you should retrieve the parseObject from server and then change and save again.

Find Object with Property in Array

is there a possibility to get an object from an array with an specific property? Or do i need to loop trough all objects in my array and check if an property is the specific i was looking for?
edit: Thanks for given me into the correct direction, but i have a problem to convert this.
// edit again: A ok, and if there is only one specific result? Is this also a possible method do to that?
let imageUUID = sender.imageUUID
let questionImageObjects = self.formImages[currentSelectedQuestion.qIndex] as [Images]!
// this is working
//var imageObject:Images!
/*
for (index, image) in enumerate(questionImageObjects) {
if(image.imageUUID == imageUUID) {
imageObject = image
}
}
*/
// this is not working - NSArray is not a subtype of Images- so what if there is only 1 possible result?
var imageObject = questionImageObjects.filter( { return $0.imageUUID == imageUUID } )
// this is not working - NSArray is not a subtype of Images- so what if there is only 1 possible result?
You have no way to prove at compile-time that there is only one possible result on an array. What you're actually asking for is the first matching result. The easiest (though not the fastest) is to just take the first element of the result of filter:
let imageObject = questionImageObjects.filter{ $0.imageUUID == imageUUID }.first
imageObject will now be an optional of course, since it's possible that nothing matches.
If searching the whole array is time consuming, of course you can easily create a firstMatching function that will return the (optional) first element matching the closure, but for short arrays this is fine and simple.
As charles notes, in Swift 3 this is built in:
questionImageObjects.first(where: { $0.imageUUID == imageUUID })
Edit 2016-05-05: Swift 3 will include first(where:).
In Swift 2, you can use indexOf to find the index of the first array element that matches a predicate.
let index = questionImageObjects.indexOf({$0.imageUUID == imageUUID})
This is bit faster compared to filter since it will stop after the first match. (Alternatively, you could use a lazy sequence.)
However, it's a bit annoying that you can only get the index and not the object itself. I use the following extension for convenience:
extension CollectionType {
func find(#noescape predicate: (Self.Generator.Element) throws -> Bool) rethrows -> Self.Generator.Element? {
return try indexOf(predicate).map({self[$0]})
}
}
Then the following works:
questionImageObjects.find({$0.imageUUID == imageUUID})
Yes, you can use the filter method which takes a closure where you can set your logical expression.
Example:
struct User {
var firstName: String?
var lastName: String?
}
let users = [User(firstName: "John", lastName: "Doe"), User(firstName: "Bill", lastName: "Clinton"), User(firstName: "John", lastName: "Travolta")];
let johns = users.filter( { return $0.firstName == "John" } )
Note that filter returns an array containing all items satisfying the logical expression.
More info in the Library Reference
Here is a working example in Swift 5
class Point{
var x:Int
var y:Int
init(x:Int, y:Int){
self.x = x
self.y = y
}
}
var p1 = Point(x:1, y:2)
var p2 = Point(x:2, y:3)
var p3 = Point(x:1, y:4)
var points = [p1, p2, p3]
// Find the first object with given property
// In this case, firstMatchingPoint becomes p1
let firstMatchingPoint = points.first{$0.x == 1}
// Find all objects with given property
// In this case, allMatchingPoints becomes [p1, p3]
let allMatchingPoints = points.filter{$0.x == 1}
Reference:
Trailing Closure
Here is other way to fetch particular object by using object property to search an object in array.
if arrayTicketsListing.contains({ $0.status_id == "2" }) {
let ticketStatusObj: TicketsStatusList = arrayTicketsListing[arrayTicketsListing.indexOf({ $0.status_id == "2" })!]
print(ticketStatusObj.status_name)
}
Whereas, my arrayTicketsListing is [TicketsStatusList] contains objects of TicketsStatusList class.
// TicketsStatusList class
class TicketsStatusList {
internal var status_id: String
internal var status_name: String
init(){
status_id = ""
status_name = ""
}
}

Resources