3

Problem

I have an array of dictionaries as follows:

var arrayOfDicts = [
    ["Id":"01", "Name":"Alice", "Age":"15"]
    ["Id":"02", "Name":"Bob", "Age":"53"]
    ["Id":"03", "Name":"Cathy", "Age":"12"]
    ["Id":"04", "Name":"Bob", "Age":"83"]
    ["Id":"05", "Name":"Denise", "Age":"88"]
    ["Id":"06", "Name":"Alice", "Age":"44"]
]

I need to remove all dictionaries where there is a duplicate name. For instance, I need an output of:

var arrayOfDicts = [
    ["Id":"01", "Name":"Alice", "Age":"15"]
    ["Id":"02", "Name":"Bob", "Age":"53"]
    ["Id":"03", "Name":"Cathy", "Age":"12"]
    ["Id":"05", "Name":"Denise", "Age":"88"]
]

Order does not need to be preserved.

Attempted Solution

for i in 0..<arrayOfDicts.count
{
    let name1:String = arrayOfDicts[i]["Name"]

    for j in 0..<arrayOfDicts.count
    {
        let name2:String = arrayOfDicts[j]["Name"]

        if (i != j) && (name1 == name2)
        {
            arrayOfDicts.remove(j)
        }
    }
} 

This crashes though, I believe since I am modifying the size of arrayOfDicts, so eventually it j is larger than the size of the array.

If someone could help me out, that would be much appreciated.

mushy
  • 43
  • 1
  • 5

7 Answers7

13

I definitely recommend having a new copy rather than modifying the initial array. I also create storage for names already used, so you should only need to loop once.

func noDuplicates(_ arrayOfDicts: [[String: String]]) -> [[String: String]] {
    var noDuplicates = [[String: String]]()
    var usedNames = [String]()
    for dict in arrayOfDicts {
        if let name = dict["name"], !usedNames.contains(name) {
            noDuplicates.append(dict)
            usedNames.append(name)
        }
    }
    return noDuplicates
}
Connor Neville
  • 7,291
  • 4
  • 28
  • 44
10

You can use a set to control which dictionaries to add to the resulting array. The approach it is very similar to the one used in these answer and this

let array: [[String : Any]] = [["Id":"01", "Name":"Alice", "Age":"15"],
                                ["Id":"02", "Name":"Bob", "Age":"53"],
                                ["Id":"03", "Name":"Cathy", "Age":"12"],
                                ["Id":"04", "Name":"Bob", "Age":"83"],
                                ["Id":"05", "Name":"Denise", "Age":"88"],
                                ["Id":"06", "Name":"Alice", "Age":"44"]]

var set = Set<String>()
let arraySet: [[String: Any]] = array.compactMap {
    guard let name = $0["Name"] as? String else { return nil }
    return set.insert(name).inserted ? $0 : nil
}

arraySet   // [["Name": "Alice", "Age": "15", "Id": "01"], ["Name": "Bob", "Age": "53", "Id": "02"], ["Name": "Cathy", "Age": "12", "Id": "03"], ["Name": "Denise", "Age": "88", "Id": "05"]]
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
1

Please check this answer:

var arrayOfDicts = [
    ["Id":"01", "Name":"Alice", "Age":"15"],
    ["Id":"02", "Name":"Bob", "Age":"53"],
    ["Id":"03", "Name":"Cathy", "Age":"12"],
    ["Id":"04", "Name":"Bob", "Age":"83"],
    ["Id":"05", "Name":"Denise", "Age":"88"],
    ["Id":"06", "Name":"Alice", "Age":"44"]
]

var answerArray = [[String:String]]()

for i in 0..<arrayOfDicts.count
{
    let name1 = arrayOfDicts[i]["Name"]
    if(i == 0){
        answerArray.append(arrayOfDicts[i])
    }else{
        var doesExist = false
        for j in 0..<answerArray.count
        {
            let name2:String = answerArray[j]["Name"]!
            if name1 == name2 {
                doesExist = true
            }
        }
        if(!doesExist){
            answerArray.append(arrayOfDicts[i])
        }
    }
}
schinj
  • 794
  • 4
  • 19
0

Several good answers already, but it was a fun exercise, so here's my solution. I'm assuming you don't care which of the duplicate entries are kept (this will keep the last one of the dupes).

func noDuplicates(arrayOfDicts: [[String:String]]) -> [[String:String]]
{
    var noDuplicates: [String:[String:String]] = [:]
    for dict in arrayOfDicts
    {
        if let name = dict["name"]
        {
            noDuplicates[name] = dict
        }
    }

    // Returns just the values of the dictionary
    return Array(noDuplicates.values.map{ $0 })
}
ghostatron
  • 2,620
  • 23
  • 27
0
let uniqueArray = Array(Set(yourArrayWithDuplicates))

That should do the trick.

If you want to use just the name for uniqueness then create these as structs.

You shouldn't be doing anything with dictionaries. Much easier to work with data that makes sense.

Fogmeister
  • 76,236
  • 42
  • 207
  • 306
  • This method does not work with the provided data type. – nathangitter May 04 '17 at 19:13
  • 1
    @nathan like I said. Change he data type to one that suits the needs of the program. A struct would allow this to be trivial and would take very little code to create and solve many more problems than just this. – Fogmeister May 04 '17 at 19:14
0

Try this:

var uniqueNames = [String: [String:String] ]()

for air in arrayOfDicts {
    if (uniqueNames[arr["Name"]!] == nil) {
        uniqueNames[arr["Name"]!] = arr
    }
}

result = Array(uniqueNames.values)
Dominic Bett
  • 478
  • 5
  • 12
0

If you don't mind using an additional list:

var uniqueArray = [[String: String]]()
for item in arrayOfDicts {
    let exists =  uniqueArray.contains{ element in
        return element["Name"]! == item["Name"]!
    }
    if !exists {
      uniqueArray.append(item)
    }
}
janusfidel
  • 8,036
  • 4
  • 30
  • 53