0

I'm trying to create an object if it doesn't exist in an array and delete one if it's duplicated. The following code can successfully determine if an object already exists and if it's duplicated, what I'm not sure about is how to capture the actual object inside the IF statement if existanceCount == 2{} within the contains(where:) method to be able to delete one of the duplicate objects.

How can I capture an object inside the contains(where:) method?

I basically need to access {$0} inside the contains(where:) method.

func dogCleanup(){
    let dogNames = ["Teddy","Browny","Mia"]

    var existanceCount = 0
    for i in 0..<dogNames.count{
        if dogs.contains(where: {$0.name == dogNames[i]}){
            existanceCount += 1
            if existanceCount == 2{
                print("Dog exists 2 times, delete it")
                // how can I capture one of the duplicate objects
            }
        }else{
            print("Does NOT exist, add dog...")
        }
    }
}

Here is in context what I'm expecting after calling the dogCleanup() method.

Example 1

    let dogs = [Dog(name: "Teddy"), Dog(name: "Browny"),Dog(name: "Mia")] // objects in Core Data
    let dogNames = ["Teddy","Browny","Mia"] // default dog names

    func dogCleanup(){
        // do nothing, both match
    }
    // What I'm expecting after running the dogCleanup
    Teddy,Browny,Mia 

Example 2

    let dogs = [Dog(name: "Teddy"), Dog(name: "Browny")] // objects in Core Data
    let dogNames = ["Teddy","Browny","Mia"]

    func dogCleanup(){
        // add Mia since it doesn't exist in Core Data
    }
    // What I'm expecting after running the dogCleanup
    Teddy, Browny, Mia 

Example 3

    let dogs = [Dog(name: "Teddy"), Dog(name: "Browny"), Dog(name: "Browny")] // objects in Core Data
    let dogNames = ["Teddy","Browny","Mia"] // default dog names

    func dogCleanup(){
        // delete one Browny and add Mia since it doesn't exist in Core Data
    }
    // What I'm expecting after running the dogCleanup
    Teddy, Browny, Mia 

Example 4

    let dogs = [Dog(name: "Teddy"), Dog(name: "Browny"), Dog(name: "Browny"), Dog(name: "Shadow")] // objects in Core Data
    let dogNames = ["Teddy","Browny","Mia"] // default dog names

    func dogCleanup(){
        // delete one Browny, add Mia and leave Shadow
    }
    // What I'm expecting after running the dogCleanup
    Teddy, Browny, Mia, Shadow
fs_tigre
  • 10,650
  • 13
  • 73
  • 146
  • 2
    Use `first(where:)` or `firstIndex(where:)` instead to get the object or the index for the object. – Joakim Danielson Aug 28 '23 at 12:49
  • 2
    For better solutions to find and remove duplicates see [this question](https://stackoverflow.com/questions/25738817/removing-duplicate-elements-from-an-array-in-swift) – Joakim Danielson Aug 28 '23 at 12:53
  • 1
    Does this need to be an *array*, specifically? It looks like a Set or Dictionary would be a more appropriate data structure that makes these kinds of operation completely trivial – Alexander Aug 28 '23 at 14:46
  • The `dogs` array is coming from Core Data. I tried converting to Set and back to Array in my CoreDataModel but doesn't remove duplicates.`let dogsArray = try manager.context.fetch(request) dogs = Array(Set(dogsArray))`. And no, I it could be a Set. Thanks. – fs_tigre Aug 28 '23 at 15:09
  • 1
    So if a dog name is a duplicate but not in the dogNames array it is ok and should not be removed? – Joakim Danielson Aug 28 '23 at 15:54
  • If dog name is in the dogNames arrey, it should exist but only once, if there are multiples of the same name, all must be removed leaving just one. I hope it makes sense. – fs_tigre Aug 28 '23 at 18:10
  • 1
    @fs_tigre `Array(Set(dogsArray))` that wouldn't work, because you would be uniqueing according to the definitions of `Dog.hash(into:)` and `Dog.==`, which probably aren't based solely off the name. – Alexander Aug 28 '23 at 19:08
  • @fs_tigre related [append if not exists](https://stackoverflow.com/a/46519116/2303865) – Leo Dabus Aug 29 '23 at 01:42

1 Answers1

1

Actually if you call this method always to pick a dog by name and create a new dog if it doesn't exist you don't have to check for more than one occurrence of the object.

func getDog(by name: String) -> Dog {

    if let dog = dogs.first(where: {$0.name == name}){
        return dog
    } else {
        dogs.append(Dog(name: name))
    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • I'm having issues trying to come up with a solution with what you're proposing since in my case the dog name `Teddy` comes from an array of strings (`dogNames`) `if let dog = dogs.first(where: {$0.name == dogNames[i]}` I have that inside a for loop, `for i in 0.. – fs_tigre Aug 28 '23 at 14:44
  • 2
    It's unclear how the categories are related to the duplicates. Does it mean that there should be only one dog per category? – vadian Aug 28 '23 at 16:12
  • No, I apologize for the confusion, `categories` shouldn't be anywhere in my code. I meant to put `dogNames`. Again, I apologize. In theory, what want is to remove duplicates and leave only the ones with the names listed in the `dogNames` array, but only one of each. – fs_tigre Aug 28 '23 at 18:06
  • 2
    My suggested approach is to avoid duplicates by checking **before** creating a new record. This is more efficient. – vadian Aug 28 '23 at 18:27
  • 1
    The check I'm doing is to create and remove duplicates for items that may get duplicated due to CloudKit Sync. These are default-objects that will be created at the start of the app but can get duplicated if the user uninstalls and reinstalls the app with CloudKit on. – fs_tigre Aug 28 '23 at 19:06
  • 1
    @fs_tigre I'm interested in providing an answer here, but I'm a little bit lost. There have been a lot of edits, two similar-but-different code snippets in the original (I don't understand how they're related), and multiple different use patterns. I'm getting lost in the sauce. Can you please edit your question down to focus on the final set of findings/details/usecases, and in particular answer some questions: 10 Can you apply unquing by-construction (so dupes never arise in the first place), or must you be able to dedupe an array that already has dupes? 2) are you doing this on every ... – Alexander Aug 28 '23 at 19:11
  • 1
    @fs_tigre CoreData load, or on each `addDog()` call (or whatever you call it), or both? 2) Does the order of the array matter? 3) If there _are_ duplicates? How should they be handled? Keep first? Keep last? Other? – Alexander Aug 28 '23 at 19:12
  • 2
    *…that may get duplicated due to CloudKit Sync*. This cannot happen if you take care of checking for duplicates as I suggested (on all devices). – vadian Aug 28 '23 at 19:23
  • Ahhh shoot, you're right, they will not be created if they already exist in Core Data. – fs_tigre Aug 28 '23 at 19:34
  • @Alexander - I edited my question and added a few samples of what I'm expecting to happen when I run the `dogsCleanup` method. – fs_tigre Aug 28 '23 at 19:40
  • 1
    You don't understand. Example 3 and 4 will never occur if you run the method to check for a duplicate **before** or **while** creating a new record. – vadian Aug 28 '23 at 19:44
  • @vadian - You just opened my eyes with something so obvious that I wasn't seeing, no duplicates will ever exist if we have a good create-if-needed method. Again, here I'm only concerned about duplicates that may happen when using CloudKit and the user uninstalls and reinstalls the app. – fs_tigre Aug 28 '23 at 19:45
  • 1
    They won't happen. – vadian Aug 28 '23 at 19:46
  • Agreed, I wasn't seeing the obvious. – fs_tigre Aug 28 '23 at 19:49
  • 1
    @fs_tigre Can you please remove the all the "edit" headings, and and squash all the content down into one single, latest version of your question taht people can read and address? The edit history is already stored automatically, people can view it if they need to. What's more important is that readers don't need to follow the plot from the start, and can just ascertain what you're truly asking at this point in time. – Alexander Aug 28 '23 at 20:41
  • Done. thank you for following alone. – fs_tigre Aug 29 '23 at 01:13
  • Just for the record, after giving it a second thought, I think there will be duplicates at some point that will need to be deleted. e.g., when the user syncs data in device1 and then goes to device2 and syncs, when the app runs on device2, the default items will be created since there is nothing in Core Data but as soon as he turns iCloud sync, there is nothing preventing from downloading from iCloud. My original thought was to check on every app load and if there were duplicates, delete one (newest date stamp). I'll leave your answer since I really like how to prevent adding duplicates. – fs_tigre Sep 02 '23 at 13:01
  • Again, there won't be duplicates. The iCloud sync algorithm is pretty smart. – vadian Sep 02 '23 at 13:20