Swift 3.0 Remove duplicates in Array of Dictionaries - arrays

I am working removing the duplicate Dictionaries in an array of Dictionaries in swift 3.0
The below is the
let Dict1 : [String : String] = ["messageTo":"Madhu"]
let Dict2 : [String : String] = ["messageTo":"Kiran"]
let Dict3 : [String : String] = ["messageTo":"Raju"]
var arrOfDict = [[String:String]]()
arrOfDict.append(Dict1)
arrOfDict.append(Dict2)
arrOfDict.append(Dict1)
arrOfDict.append(Dict3)
arrOfDict.append(Dict2
print(arrOfDict)
//prints [["messageTo": "Madhu"], ["messageTo": "Kiran"], ["messageTo": "Madhu"], ["messageTo": "Raju"], ["messageTo": "Kiran"]]
As you can see there are 2 duplicate dictionaries in the arrOfDict.
can any one help me out in filtering the duplicates using Set or any other approach

Dictionaries does not conform to Hashable (or Equatable), so using Set is not an available approach in this case. For dictionaries where Key and Value types are Equatable, however, we have access to the == operator for readily performing a uniqueness filtering of the array of dictionaries:
public func ==<Key : Equatable, Value : Equatable>(
lhs: [Key : Value], rhs: [Key : Value]) -> Bool
E.g. as follows (O(n^2))
arrOfDict = arrOfDict.enumerated()
.filter { (idx, dict) in !arrOfDict[0..<idx].contains(where: {$0 == dict}) }
.map { $1 }
print(arrOfDict)
// [["messageTo": "Madhu"], ["messageTo": "Kiran"], ["messageTo": "Raju"]]
// or ...
arrOfDict = arrOfDict.enumerated()
.flatMap { (idx, dict) in !arrOfDict[0..<idx].contains(where: {$0 == dict}) ? dict : nil }

func removeDuplicates(_ arrayOfDicts: [[String: String]]) -> [[String: String]] {
var removeDuplicates = [[String: String]]()
var arrOfDict = [String]()
for dict in arrayOfDicts {
if let name = dict["messageTo"], ! arrOfDict.contains(name) {
removeDuplicates.append(dict)
arrOfDict.append(name)
}
}
return removeDuplicates
}

The reason why it is duplicated, because you add Dict1 and Dict2 2 times. May i ask why?
Set would not work for you, because Dictionary type does not conform to the Hashable protocol, only its key property.
You can do a validation on the dictionary, if there is already a value like that, do not append it to arrOfDict.

you can try this :
var set = NSSet(array: arrOfDict)

Related

How Can I Put a Dictionary Inside An Array That's Nested In Another Dictionary? (Swift)

I want to add an array of dictionaries to an existing dictionary.
var details: [String : [String : String]] = [:]
viewDidLoad()
...
for i in 0..<count {
let hour = makeHourString()
details[hour] = [String: String] () as [String : String]
let dict = ["detailKey": "2100xx", "detailImage":"base64xx"]
details[hour].append(dict)
}
...
It is telling me: Value of type '[String : String]' has no member 'append.' But shouldn't it since it's an array?
I can get this kind of nested dictionary if I change the code to:
var details: [String: [Any]] = [:]
viewDidLoad()
...
for i in 0..<count {
let hour = makeHourString()
details[hour] = [String] () as [String]
let dict = ["detailKey": "2100xx", "detailImage":"base64xx"]
details[hour].append(dict)
}
...
Unfortunately, this isn't working for me because I need to able to store my data source using Codable. Any help is greatly appreciated.
This [String : String] is a Dictionary not an Array , According to your case you need
var details: [String: [[String:String]]] = [:]
and off course you can do this also
var details: [String:[Item]] = [:]
With
struct Item {
let detailKey, detailImage:String
}

Sort an array of dictionaries by a certain key value (Swift 3)

I was looking for a way to sort an array of invoice dictionaries based on invoice ID (which is a value of one of the keys in the dictionary). I couldn't find a solution that worked as well as I would've liked so I wrote out a function which works well for me and I thought I'd share it with the community.
It's pretty simple, you just pass in the array, pass in the key to sort it by and it returns the array sorted.
let testArray: Array<Dictionary<String, String>> = sortDictArrayByKey(arr: someUnsortedArray, key: keyToSort)
func sortDictArrayByKey(arr: Array<Dictionary<String, String>>, key: String) -> Array<Dictionary<String, String>>{
var keyArray = Array<String>()
var usedKeys = Array<String>()
for object in arr{
keyArray.append(object[key]!)
}
keyArray.sort(by: <)
var newArray = Array<Dictionary<String, String>>()
for keyVal in keyArray{
// Check if we've already seen this entry, if so, skip it
if usedKeys.contains(keyVal){
continue
}
usedKeys.append(keyVal)
// Check for duplicate entries
var tempArray = Array<Dictionary<String, String>>()
for object in arr{
if object[key] == keyVal{
tempArray.append(object)
}
}
for item in tempArray{
newArray.append(item)
}
tempArray.removeAll()
}
return newArray
}
Extension
You can created an extension available for Arrays of Dictionaries where both the Key and the Value are String(s).
extension Array where Element == [String:String] {
func sorted(by key: String) -> [[String:String]] {
return sorted { $0[key] ?? "" < $1[key] ?? "" }
}
}
Example
let crew = [
["Name":"Spook"],
["Name":"McCoy"],
["Name":"Kirk"]
]
crew.sorted(by: "Name")
// [["Name": "Kirk"], ["Name": "McCoy"], ["Name": "Spook"]]

How to check if my array of dictionaries has certain dictionary?

I have two arrays of dictionaries with type [String: String].
How can I check if one array contains dictionary from another.
let firstArray: [[String: String]] = [dict1, dict2, dict3]
let secondArray: [[String: String]] = [dict1, dict2, dict3, dict4, dict5]
I tried to do this with contains() method
for item in firstArray {
if secondArray.contains(item) {
print("Hello")
}
}
but it throw an error there. So what's the best way to do this?
You can use the predicate form of contains to accomplish this:
for item in firstArray {
if secondArray.contains(where: { $0 == item }) {
print("Hello")
}
}
You can't use the other form of contains because type [String : String] does not conform to the Equatable protocol.
This works fine in Swift 4
let dictionary = ["abc":"pqr"]
if !myArray.contains{ $0 == dictionary } {
//append dictionary inside array
myArray.append(dictionary)
}
else {
//dictionary already exist in your myArray
}

Extend Swift Array to Filter Elements by Type

How can a swift array be extended to access members of a particular type?
This is relevant if an array contains instances of multiple classes which inherit from the same superclass. Ideally it would enforce type checking appropriately.
Some thoughts and things that don't quite work:
Using the filter(_:) method works fine, but does enforce type safety. For example:
protocol MyProtocol { }
struct TypeA: MyProtocol { }
struct TypeB: MyProtocol { }
let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
let filteredArray = myStructs.filter({ $0 is TypeA })
the filteredArray contains the correct values, but the type remains [MyProtocol] not [TypeA]. I would expect replacing the last with let filteredArray = myStructs.filter({ $0 is TypeA }) as! [TypeA] would resolve that, but the project fails with EXEC_BAD_INSTRUCTION which I do not understand. Perhaps type casting arrays is not possible?
Ideally this behavior could be wrapped up in an array extension. The following doesn't compile:
extension Array {
func objectsOfType<T:Element>(type:T.Type) -> [T] {
return filter { $0 is T } as! [T]
}
}
Here there seem to be at least two problems: the type constraint T:Element doesn't seem to work. I'm not sure what the correct way to add a constraint based on a generic type. My intention here is to say T is a subtype of Element. Additionally there are compile time errors on line 3, but this could just be the same error propagating.
SequenceType has a flatMap() method which acts as an "optional filter":
extension SequenceType {
/// Return an `Array` containing the non-nil results of mapping
/// `transform` over `self`.
///
/// - Complexity: O(*M* + *N*), where *M* is the length of `self`
/// and *N* is the length of the result.
#warn_unused_result
#rethrows public func flatMap<T>(#noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}
Combined with matt's suggestion to use as? instead of is you
can use it as
let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
let filteredArray = myStructs.flatMap { $0 as? TypeA }
Now the type of filteredArray is inferred as [TypeA].
As an extension method it would be
extension Array {
func objectsOfType<T>(type:T.Type) -> [T] {
return flatMap { $0 as? T }
}
}
let filteredArray = myStructs.objectsOfType(TypeA.self)
Note: For Swift >= 4.1, replace flatMap by compactMap.
Instead of testing (with is) how about casting (with as)?
let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
var filteredArray = [TypeA]()
for case let t as TypeA in myStructs {filteredArray.append(t)}
Casting arrays does not work in Swift. This is because arrays in Swift use generics, just like you can't cast a custom class, where only the type T changes. (class Custom<T>, Custom<Int>() as! Custom<String>).
What you can do is create an extension method to Array, where you define a method like this:
extension Array {
func cast<TOut>() -> [TOut] {
var result: [TOut] = []
for item in self where item is TOut {
result.append(item as! TOut)
}
return result
}
}
I think the canonical FP answer would be to use filter, as you are, in combination with map:
let filteredArray = myStructs.filter({ $0 is TypeA }).map({ $0 as! TypeA })
alternatively, you can use reduce:
let filtered2 = myStructs.reduce([TypeA]()) {
if let item = $1 as? TypeA {
return $0 + [item]
} else {
return $0
}
}
or, somewhat less FP friendly since it mutates an array:
let filtered3 = myStructs.reduce([TypeA]()) { ( var array, value ) in
if let item = value as? TypeA {
array.append(item)
}
return array
}
which can actually be shortened into the once again FP friendly flatMap:
let filtered4 = myStructs.flatMap { $0 as? TypeA }
And put it in an extension as:
extension Array {
func elementsWithType<T>() -> [T] {
return flatMap { $0 as? T }
}
}
let filtered5 : [TypeA] = myStructs.elementsWithType()

Swift loop through array of dictionaries

Why am I getting this error when looping through an array of dictionaries ?
import UIKit
let tableView = UITableView()
func meh() {
let product = buildCells()
for (identifier, nib) in product {
tableView.registerNib(nib, forCellReuseIdentifier: identifier)
}
}
func buildCells() -> [[String: UINib]] {
var collector = [[String: UINib]]()
let identifier = "identifier"
let nib = UINib(nibName: "TableViewCell", bundle: nil)
let asdf = [identifier: nib];
collector.append(asdf)
return collector
}
The forin loop in the meh() method produces the following error:
'Dictionary' is not convertible to '([[String : UINib]], [[String : UINib]])'
We can't iterate through the keys and values of an array of dictionaries. We can iterate through the dictionaries though. And for each dictionary, we can iterate through its keys and values:
let product = buildCells()
for dict in product {
for (identifier, nib) in dict {
tableview.registerNib(nib, forCellReuseIdentifier: identifier)
}
}
But I think the problem is actually more likely the nonsense going on in buildCells(). Why are you returning an array of dictionaries? Do you plan to have duplicate keys? I don't think the tableview will let you register multiple nibs for the same identifier.
Why don't we just return a dictionary?
func buildCells() -> [String: UINib] {
var dict = [String: UINib]()
dict["identifier"] = UINib(nibName: "TableViewCell", bundle: nil)
// rinse & repeat for all of your other ID/nib combos with no duplicate IDs
return dict
}
Now we can iterate over the key/value pairs in the dictionary without the outer loop:
for (identifier, nib) in dict {
tableview.registerNib(nib, forCellReuseIdentifier: identifier)
}

Resources