Remove duplicate values from a dictionary in Swift 3 - ios

Hi I have a dictionary and I just wanna remove duplicate values (with their keys) like so :
var myDict : [Int:String] = [1:"test1", 2:"test2", 3:"test1", 4:"test4"]
Desired output :
[1: "test1", 2: "test2", 4: "test4"]

It looks to me like all the other answers will have O(n^2) performance.
Here is a solution that should operate in O(n) time:
var sourceDict = [1:"test1", 2:"test2", 3:"test1", 4:"test4"]
var uniqueValues = Set<String>()
var resultDict = [Int:String](minimumCapacity: sourceDict.count)
//The reserveCapacity() function doesn't exist for Dictionaries, as pointed
//out by Hamish in the comments. See the initializer with minimumCapacity,
//above. That's the way you have to set up a dictionary with an initial capacity.
//resultDict.reserveCapacity(sourceDict.count)
for (key, value) in sourceDict {
if !uniqueValues.contains(value) {
uniqueValues.insert(value)
resultDict[key] = value
}
}
For small dictionaries the difference is insignificant, but if you have a dictionary with hundreds (or thousands) of key/value pairs then the performance of an n^2 algorithm starts to get really bad.

var myDict: [Int:String] = [1:"test1", 2:"test2", 3:"test1", 4:"test4"]
var result: [Int:String] = [:]
for (key, value) in myDict {
if !result.values.contains(value) {
result[key] = value
}
}
print(result)

This is another way of doing the same
var myDict : [Int:String] = [1:"test1", 2:"test1", 3:"test1", 4:"test4", 5:"test4"]
var newDict:[Int: String] = [:]
for (key, value) in myDict {
print(key, value)
let keys = myDict.filter {
return $0.1.contains(value)
}.map {
return $0.0
}
if keys.first == key {
newDict[key] = value
}
}
print(newDict)

You can use this code
let newDict = myDict.keys.sorted().reduce([Int:String]()) { (res, key) -> [Int:String] in
guard let value = myDict[key], !res.values.contains(value) else { return res }
var res = res
res[key] = value
return res
}
Please keep in mind that dictionary are not sorted so the output could be something like this
[2: "test2", 4: "test4", 1: "test1"]
Please refer to the answer provided by #Duncan for a faster solution.

Related

Iterate through Dictionary over all levels

I have a data structure in a Dictionary which looks like this:
- device
- type
- isActive
- Data
- Manufacturer
- Build Date
- Power
...
- Example
Now if I create a For Loop it only shows me the Values of the First level which means
type, isActive, Data, Example
for (key, value) in value {
}
All below Data like Build Date or Power is Missing - how can I irritate through this levels also?
Assuming you have Dictionary with type [String:Any], function to flatten it will be something like:
func flatten(_ obj:[String:Any]) -> [String:Any] {
var result = [String:Any]()
for (key, val) in obj {
if let v = val as? [String:Any] {
result = result.merging(flatten(v), uniquingKeysWith: {
$1
})
}
//I also included initial value of internal dictionary
/if you don't need initial value put next line in 'else'
result[key] = val
}
return result
}
To use that:
let d:[String:Any] = [
"test1":1,
"test2":2,
"test3":[
"test3.1":31,
"test3.2":32
]
]
let res = flatten(d)
print(res)
["test2": 2, "test3.2": 32, "test3.1": 31, "test1": 1, "test3":
["test3.2": 32, "test3.1": 31]]
note: dictionaries are not sorted structures
You are dealing with a Recursion problem here.
You could walk the dictionary level by level:
func walk(dictionary: [String: Any]) {
for (key, value) in dictionary {
print("\(key): \(value)")
if let value = value as? [String: Any] {
walk(dictionary: value)
}
}
}
You can change [String: Any] type with your dictionary type (NSDictionary, etc.)
As Tomas says, this can be solved with recursion. His "walk" function simply prints the values on separate lines, but with no keys, and not formatting. Here is code that logs nested dictionaries using the structure you outlined:
//Define the keys we use in our example
enum keys: String {
case device
case type
case isActive
case Data
case Manufacturer
case BuildDate
case Power
case Example
}
//Create a sample dictionary using those keys and some random values
let dict = [keys.device.rawValue:
[keys.type.rawValue: "appliance",
keys.isActive.rawValue: true,
keys.Data.rawValue:
[keys.Manufacturer.rawValue: "GE",
keys.BuildDate.rawValue: "12/23/96",
keys.Power.rawValue: 23,
],
keys.Example.rawValue: "Foo"
]
]
//Create an extension of Dictionary for dictionaries with String Keys.
extension Dictionary where Key == String {
//Define a function to recursively build a description of our dictionary
private func descriptionOfDict(_ aDict: [String: Any], level: Int = 0) -> String {
var output = ""
var indent: String = ""
if level > 0 {
output += "\n"
for _ in 1...level {
indent += " "
}
}
for (key,value) in aDict {
output += indent + key + ": "
if let subDict = value as? [String: Any] {
//If the value for this key is another dictionary, log that recursively
output += descriptionOfDict(subDict, level: level+1)
} else {
if let aString = value as? String {
output += "\"" + aString + "\"\n"
} else {
output += String(describing: value) + "\n"
}
}
}
return output
}
//Add a description property for dictionaries
var description: String {
return descriptionOfDict(self)
}
}
print(dict.description)
That outputs:
device:
isActive: true
Data:
Manufacturer: "GE"
Power: 23
BuildDate: "12/23/96"
Example: "Foo"
type: "appliance"
Edit:
The above, defining a String property description, changes the output when you print a [String:Any] dictionary. If you don't want that, rename the property description to something else like dictDescription

Remove duplicates from array of dictionaries, Swift 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.
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
}
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"]]
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])
}
}
}
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 })
}
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.
Try this:
var uniqueNames = [String: [String:String] ]()
for air in arrayOfDicts {
if (uniqueNames[arr["Name"]!] == nil) {
uniqueNames[arr["Name"]!] = arr
}
}
result = Array(uniqueNames.values)
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)
}
}

Create an Array of Object from an Array of Dictionaries with Swift

I'm receiving a JSON dictionary from a web service and I need to map the return values to existing values. Here's essentially what I'm trying to do:
class Contract {
var contractID: String?
var ebState: String?
var ibState: String?
var importerState: String?
var exportersBankRefNo: String?
var importersBankRefNo: String?
}
let contract1 = Contract()
contract1.contractID = "001"
let contract2 = Contract()
contract2.contractID = "002"
// This is the JSON return dictionary
let exportAppnStatusList: [[String: String]] = [["contractID":"001",
"ExporterBankRefNo":"ExporterBankRefNo001",
"ExporterBankState":"ACCEPTED",
"ImporterBankRefNo":"",
"ImporterBankState":"UNKNOWN",
"ImporterState":"UNKNOWN" ],
["contractID":"002",
"ExporterBankRefNo":"ExporterBankRefNo002",
"ExporterBankState":"ACCEPTED",
"ImporterBankRefNo":"ImporterBankRefNo002",
"ImporterBankState":"ACCEPTED",
"ImporterState":"UNKNOWN" ]]
I need to take the exportAppnStatusList and fill in the associated values in the existing contract1 and contract2, mapping by the contractID
This fills the contracts with available information, it ignores contracts where the id could not be found:
for contract in [contract1, contract2] {
if let contractDict = exportAppnStatusList.filter({$0["contractID"] == contract.contractID}).first {
contract.exportersBankRefNo = contractDict["ExporterBankRefNo"]
contract.ebState = contractDict["ExporterBankState"]
contract.importersBankRefNo = contractDict["ImporterBankRefNo"]
contract.ibState = contractDict["ImporterBankState"]
contract.importerState = contractDict["ImporterState"]
}
}
Why not generate the contract object by mapping over the array of dictionaries like this? You'll need to write a custom initializer that takes all these params
exportAppnStatusList.map { (dict:[Stirng:String]) -> Contract in
return Contract(contractID:dict["contractID"],
ebState:dict["ExporterBankState"],
ibState:dict["ImporterBankState"],
importerState:dict["ImporterState"],
exportersBankRefNo:dict["ExporterBankRefNo"],
importersBankRefNo:dict["ImporterBankRefNo"]
}
Try using this init (your class must inherit from NSObject):
init(jsonDict: [String: String]) {
super.init()
for (key, value) in jsonDict {
if class_respondsToSelector(Contract.self, NSSelectorFromString(key)) {
setValue(value, forKey: key)
}
}
}
Then you can do this:
exportAppnStatusList.forEach {
print(Contract(jsonDict: $0))
}

Swift filter dictionary error: Cannot assign a value of type '[(_, _)]' to a value of type '[_ : _]'

I am trying to filter a dictionary in swift:
var data: [String: String] = [:]
data = data.filter { $0.1 == "Test" }
the filter code above compiles under Swift 2 but yields the following error:
Cannot assign a value of type '[(String, String)]' to a value of type '[String : String]'
is this a bug in the Swift compiler or is this not the right way to filter dictionaries in Swift?
This has been fixed in Swift 4
let data = ["a": 0, "b": 42]
let filtered = data.filter { $0.value > 10 }
print(filtered) // ["b": 42]
In Swift 4, a filtered dictionary returns a dictionary.
Original answer for Swift 2 and 3
The problem is that data is a dictionary but the result of filter is an array, so the error message says that you can't assign the result of the latter to the former.
You could just create a new variable/constant for your resulting array:
let data: [String: String] = [:]
let filtered = data.filter { $0.1 == "Test" }
Here filtered is an array of tuples: [(String, String)].
Once filtered, you can recreate a new dictionary if this is what you need:
var newData = [String:String]()
for result in filtered {
newData[result.0] = result.1
}
If you decide not to use filter you could mutate your original dictionary or a copy of it:
var data = ["a":"Test", "b":"nope"]
for (key, value) in data {
if value != "Test" {
data.removeValueForKey(key)
}
}
print(data) // ["a": "Test"]
Note: in Swift 3, removeValueForKey has been renamed removeValue(forKey:), so in this example it becomes data.removeValue(forKey: key).
data.forEach { if $1 != "Test" { data[$0] = nil } }
Just another approach (a bit simplified) to filter out objects in your dictionary.
Per Apple docs, filter:
Returns an array containing, in order, the elements of the sequence that satisfy the given predicate.
https://developer.apple.com/reference/swift/sequence/1641239-filter
I found myself needing to do what the OP was asking about and ended up writing the following extensions (Swift 3):
extension Dictionary
{
func filteredDictionary(_ isIncluded: (Key, Value) -> Bool) -> Dictionary<Key, Value>
{
return self.filter(isIncluded).toDictionary(byTransforming: { $0 })
}
}
extension Array
{
func toDictionary<H:Hashable, T>(byTransforming transformer: (Element) -> (H, T)) -> Dictionary<H, T>
{
var result = Dictionary<H,T>()
self.forEach({ element in
let (key,value) = transformer(element)
result[key] = value
})
return result
}
}
Usage:
let data = ["a":"yes", "b":"nope", "c":"oui", "d":"nyet"]
let filtered = data.filteredDictionary({ $0.1 >= "o" })
// filtered will be a dictionary containing ["a": "yes", "c": "oui"]
I've found this method to be useful after filtering or applying some other transform that results in an array of dictionary elements:
extension Array {
func dictionary<K: Hashable, V>() -> [K: V] where Element == Dictionary<K, V>.Element {
var dictionary = [K: V]()
for element in self {
dictionary[element.key] = element.value
}
return dictionary
}
}
To use it, just say something like:
dictionary = dictionary.filter{ $0.key == "test" }.dictionary()
The advantage of this method is that no argument of any kind needs to be passed to the dictionary() method. Generic type arguments tell the compiler everything it needs to know.
You can arrange ascending order according to dictionary value using filter
let arrOfDict = [{"ABC":24},{"XYZ":21},{"AAA":23}]
let orderedDict = arrOfDict.filter{$0.value < $1.value}
you will get below output:
[
{ "XYZ": 21 },
{ "AAA": 23 },
{ "ABC": 24 }
]

How can I merge two arrays into a dictionary?

I have 2 arrays:
var identic = [String]()
var linef = [String]()
I've appended them with data. Now for usability purposes my goal is to combine them all into a dictionary with the following structure
FullStack = ["identic 1st value":"linef first value", "identic 2nd value":"linef 2nd value"]
I've been browsing around the net and couldnt find a viable solution to this.
Any ideas are greatly appreciated.
Thank you!
Starting with Xcode 9.0, you can simply do:
var identic = [String]()
var linef = [String]()
// Add data...
let fullStack = Dictionary(uniqueKeysWithValues: zip(identic, linef))
If your keys are not guaranteed to be unique, use this instead:
let fullStack =
Dictionary(zip(identic, linef), uniquingKeysWith: { (first, _) in first })
or
let fullStack =
Dictionary(zip(identic, linef), uniquingKeysWith: { (_, last) in last })
Documentation:
https://developer.apple.com/documentation/swift/dictionary/init(uniquekeyswithvalues:)
https://developer.apple.com/documentation/swift/dictionary/init(_:uniquingkeyswith:)
Use enumerated():
var arrayOne: [String] = []
var arrayTwo: [String] = []
var dictionary: [String: String] = [:]
for (index, element) in arrayOne.enumerated() {
dictionary[element] = arrayTwo[index]
}
The pro approach would be to use an extension:
extension Dictionary {
public init(keys: [Key], values: [Value]) {
precondition(keys.count == values.count)
self.init()
for (index, key) in keys.enumerate() {
self[key] = values[index]
}
}
}
Edit: enumerate() → enumerated() (Swift 3 → Swift 4)
A slightly different method, which doesn't require the arrays to be of the same length, because the zip function will safely handle that.
extension Dictionary {
init(keys: [Key], values: [Value]) {
self.init()
for (key, value) in zip(keys, values) {
self[key] = value
}
}
}
If you'd like to be safer and ensure you're picking the smaller array count each time (so that you're not potentially crashing if the second array is smaller than the first), then do something like:
var identic = ["A", "B", "C", "D"]
var linef = ["1", "2", "3"]
var Fullstack = [String: String]()
for i in 0..<min(linef.count, identic.count) {
Fullstack[identic[i]] = linef[i]
}
print(Fullstack) // "[A: 1, B: 2, C: 3]"
This is a generic solution
func dictionaryFromKeysAndValues<K : Hashable, V>(keys:[K], values:[V]) -> Dictionary<K, V>
{
assert((count(keys) == count(values)), "number of elements odd")
var result = Dictionary<K, V>()
for i in 0..<count(keys) {
result[keys[i]] = values[i]
}
return result
}
var identic = ["identic 1st value", "identic 2nd value", "identic 3rd value"]
var linef = ["linef 1st value", "linef 2nd value", "linef 3rd value"]
let mergedDictionary = dictionaryFromKeysAndValues(identic, linef)
Here is a extension that combines some of the previous answers and accepts all Sequences, not only Arrays.
public extension Dictionary {
init<K: Sequence, V: Sequence>(keys: K, values: V) where K.Element == Key, V.Element == Value, K.Element: Hashable {
self.init()
for (key, value) in zip(keys, values) {
self[key] = value
}
}
}
That extension doesn't require the sequences to be the same lengths. If you want that, here is an extension with assertions.
public extension Dictionary {
init<K: Sequence, V: Sequence>(keys: K, values: V) where K.Element == Key, V.Element == Value, K.Element: Hashable {
self.init()
var keyIterator = keys.makeIterator()
for value in values {
let key = keyIterator.next()
assert(key != nil, "The value sequence was longer.")
self[key!] = value
}
assert(keyIterator.next() == nil, "The key sequence was longer.")
}
}

Resources