Swift 3 - Difference between two arrays of dictionary - arrays

I have two arrays of dictionaries:
let arrayA = [["name1": "email1"], ["name2": "email2"]]
let arrayB = [["name1": "email1"], ["name3": "email3"]]
I want to compare them and get another two arrays: arrayC should have the elements in arrayA but not in arrayB, and arrayD should have the elements in arrayB but not in arrayA:
let arrayC = [["name2": "email2"]]
let arrayD = [["name3": "email3"]]
How can I do this taking into the consideration large arrays?

Here you go
let arrayA = [["name1": "email1"], ["name2": "email2"]]
let arrayB = [["name1": "email1"], ["name3": "email3"]]
let arrayC = arrayA.filter{
let dict = $0
return !arrayB.contains{ dict == $0 }
}
let arrayD = arrayB.filter{
let dict = $0
return !arrayA.contains{ dict == $0 }
}

I know this answer may be complicating things and that you can use filter but...have you considered using Sets instead of Arrays?
Sets can give you operations for finding elements in setA but not in setB or elements in setA and setB out of the box.
There are a few caveats about sets though. As it says in The Swift Programming Guide
A set stores distinct values of the same type in a collection with no defined ordering. You can use a set instead of an array when the order of items is not important, or when you need to ensure that an item only appears once.
notice from the above:
Distinct: Meaning no duplicates
No definded ordering: Means that you cannot expect your sets to be in order
Also, notice this (also from The Swift Programming Guide):
A type must be hashable in order to be stored in a set—that is, the type must provide a way to compute a hash value for itself.
If you can live with that...then sets are a fine solution I think.
Here is an example...I created a simple Email struct and made that implement Hashable:
struct Email {
let name: String
let email: String
}
extension Email: Hashable {
var hashValue: Int {
return "\(name)\(email)".hashValue
}
static func ==(lhs: Email, rhs: Email) -> Bool {
return lhs.name == rhs.name && lhs.email == rhs.email
}
}
And that can then be used like so:
let arrayA = [Email(name: "name1", email: "email1"), Email(name: "name2", email: "email2")]
let arrayB = [Email(name: "name1", email: "email1"), Email(name: "name3", email: "email3")]
let setA = Set(arrayA)
let setB = Set(arrayB)
let inBothAAndB = setA.intersection(setB) //gives me an Email with "name1", "email1"
let inAButNotB = setA.subtracting(setB) //gives me an Email with "name2", "email2"
let inBButNotA = setB.subtracting(setA) //gives me an Email with "name3", "email3"
So...I don't know if that confuses things for you or makes things harder or maybe impossible (if you're data can contain more than one element with the same name and email for instance) but...I just thought you should consider sets :)
Hope that helps you.

Related

Looping over and combining Swift array of structs?

I'm trying to create an array of structs (User structs below) where, if the user.name does not yet exist in the array, it appends the user -- but if the user.name is already present ("Mcdonalds" in the below example), then it will simply add the item.amount to the existing struct.
In other words, the below code should create an array of 4 Users, with the User Mcdonalds item.amount totaling 23.44 + 12.33.
I remember doing this sort of thing in JS no sweat, but I can't for the life of me figure out how to do this in Swift. Thanks for any help!
struct User {
var name: String
var amount: Double
}
var user1 = User(name: "Mcdonalds", amount: 23.44)
var user2 = User(name: "Wendys", amount: 15.44)
var user3 = User(name: "Cabanos", amount: 12.22)
var user4 = User(name: "Shell Gas", amount: 23.33)
var user5 = User(name: "Mcdonalds", amount: 12.33)
To loop over the users they'll need to be in an array to start.
Then you can use .reduce(into:) to reduce them into one condensed dictionary (the dictionary will allow you to have a unique key (the name of the user here) so that you don't have duplicate entries). Then you can use .map() to just get the value and not the key of that dictionary so that the final result will be an array of users.
struct User {
var name: String
var amount: Double
}
var users = [
User(name: "Mcdonalds", amount: 23.44),
User(name: "Wendys", amount: 15.44),
User(name: "Cabanos", amount: 12.22),
User(name: "Shell Gas", amount: 23.33),
User(name: "Mcdonalds", amount: 12.33)
]
var reducedUsers = users.reduce(into: [String: User]()) { (result, nextUser) in
if let existing = result[nextUser.name] {
result[nextUser.name] = User(name: nextUser.name, amount: existing.amount + nextUser.amount)
} else {
result[nextUser.name] = nextUser
}
}.map { $0.value }
A clean and swifty way is to write an extension for Array. Swift is highly protocol-oriented, which means you are able to extend any existing system or self-written class with new functions.
This is just a simple implementation, which uses a function to append or update any given user object:
extension Array where Element == User {
/// Appends a not existing user to the array or updates the amount value if user is already present
mutating func appendOrUpdate(_ userObject: Element) {
// Check if user is already in the array
if let index = self.firstIndex(where: { $0.name == userObject.name }) {
// Update the amount
self[index].amount += userObject.amount
}
else {
// Append the user
self.append(userObject)
}
}
}
As the where clause specifies the extension the Element of the array only to be applied when the given object is your User struct, it is only available when you pass in an user object.
Usage:
var userArray: [User] = []
userArray.appenOrUpdate(User(name: "Mcdonalds", amount: 23.44))
userArray.appenOrUpdate(User(name: "Wendys", amount: 15.44))
userArray.appenOrUpdate(User(name: "Cabanos", amount: 12.22))
userArray.appenOrUpdate(User(name: "Shell Gas", amount: 23.33))
userArray.appenOrUpdate(User(name: "Mcdonalds", amount: 12.33))
This will result in an array with just four entries and the double entry 'Mcdonalds' user's amount cumulated.
Note that the function has the mutating keyword in front, as if not you will not be able to modify the array and its entries. This is necessary due the nature of arrays being Structs themselves.
You can also write a function like the know Array's append(contentsOf:) and pass in an array of user objects and loop through them updating or appending the given objects.
Best way is to put this extension in a separate file called Array+User.swift according to best practise naming conventions.
You can read more about extensions in Swift and their power here: https://docs.swift.org/swift-book/LanguageGuide/Extensions.html
Matthew Gray's answer is very good, and can be used for a wide variety of problems that may be more complex than this one. But for this specific problem, it can be done much more simply.
let reducedUsers = users.reduce(into: [:]) { (result, user) in
result[user.name, default: 0] += user.amount
}
.map(User.init)
The point of this is that it tears apart the struct into key and value in a Dictionary, and then reassembles the values into an Array at the end. Swift is smart enough to figure out the type of the [:], so there's no need to specify that.
Note that there is a time-space tradeoff here. This creates a temporary Dictionary that can be very large. If this kind of operation is common and the dataset is large, you should consider storing this data in a Dictionary ([String: User] or [String: Double]) all the time rather than converting back and forth.

Using returned value (array of Strings) in another Swift file

Apologize for a complete newbie question. This was the original array of Strings I wrote:
let fruit = ["apple1.jpg", "apple2.jpg", "apple3.jpg", ..... "apple10.jpg"]
First, I made a function in a separate Swift file (attached to the project) to replace above array, as the array's content might be changed based on several input factors later:
class Fruits {
let fruit = "apple"
func fruitName() -> [String] {
let arrayA = (1...10).map({ "\(fruit)\($0).jpg" })
return arrayA
}
}
}
This is everything written on Fruits.swift file. And then, back to original file, I wanted to replace the original let fruit = ["", "", ...] to something like let fruit = Fruits.fruitName() - by loading the returned arrayA. But it was a bit confusing to understand how to use returned String Array values in a different file, inside of a different Class bracket. I tried something like let fruits = Fruits(), let fruit = fruits.fruitName(), etc but it doesn't seem to successfully replace the original array code. I still need to create the constant let fruit = part. Is there any way to load the returned value in a separate file? Much appreciated. <3
If you want a property of a class to be directly accessible from anywhere in your code, you can make it static:
class Fruits {
static let fruit = "apple"
static func fruitName() -> [String] {
let arrayA = (1...10).map({ "\(fruit)\($0).jpg" })
return arrayA
}
}
// usage:
let fruits = Fruits.fruitName()
Depending on your specific situation, you could even not have a class and have a global function that takes the fruit as a parameter:
func fruitNames(fruit: String) -> [String] {
let arrayA = (1...10).map({ "\(fruit)\($0).jpg" })
return arrayA
}
// usage:
let fruits = fruitNames(fruit: "apple")

Is there approach to map dictionary keys/values simultaneously in Swift?

Referring to: Swift Standard Library > Dictionary > map(_:)
Returns an array containing the results of mapping the given closure
over the sequence’s elements.
As mentioned, we can do mapping in dictionaries, but the output will be an array, not a "mapped" dictionary.
Honestly, I'm not pretty sure if saying "mapping the whole dictionary" is legal, but what I mean is the following:
Consider that we have:
let myDict = ["1": "one","2": "tow","3": "three"]
and we want to map the whole thing! (both keys and values). Output should be:
let mappedDict = ["03": "THREE", "02": "TOW", "01": "ONE"]
Let's assume that the goal of the mapping is to add "0" as a first character to all keys and let all values to be upper-cased.
To make it more readable, I posted a solution (what I tried) as an answer instead of mentioning it in the question it self; I think my answer is not so elegant (or at least how I feel about its code smell), I mapped the keys, the values and combine them in a dictionary, each step has been achieved independently.
So, What I am asking about is:
Is there a way to do this job directly in one step? Something similar to:
This snippet is a demonstration of what I'm asking about, code won't work fine
let myDict = ["1": "one","2": "tow","3": "three"]
let mappedDict = myDict.map { key, value in
"0" + key
value.uppercased()
}
Thanks in advance.
How about this?
let myDict = ["1": "one","2": "tow","3": "three"]
let mappedDict = myDict.reduce([:]) { (result, pair) -> [String: String] in
var result = result
result["0" + pair.key] = pair.value.uppercased()
return result
}
You can achieve this by doing the following:
let myDict = ["1": "one","2": "tow","3": "three"]
let mappedKeys = myDict.map { "0" + $0.key } // ["02", "01", "03"]
let mappedValues = myDict.map { $0.value.uppercased() } // ["TOW", "ONE", "THREE"]
var mappedDict = [String: String]()
let zippedArray = Array((zip(mappedKeys, mappedValues)))
for element in zippedArray {
mappedDict[element.0] = element.1
}
print(mappedDict) // ["03": "THREE", "02": "TOW", "01": "ONE"]
To be more clear, the above code snippet doing the following:
Mapping the dictionary keys.
Mapping the dictionary values.
Create a new empty dictionary mappedDict to append to it.
Combining mapped keys/values into zippedArray (using zip).
Filling mappedDict via for-loop.

Swift: Accessing array value in array of dictionaries

I am currently struggling with obtaining a value from an array inside an array of dictionaries. Basically I want to grab the first "[0]" from an array stored inside an array of dictionaries. This is basically what I have:
var array = [[String:Any]]()
var hobbies:[String] = []
var dict = [String:Any]()
viewDidLoad Code:
dict["Name"] = "Andreas"
hobbies.append("Football", "Programming")
dict["Hobbies"] = hobbies
array.append(dict)
/// - However, I can only display the name, with the following code:
var name = array[0]["Name"] as! String
But I want to be able to display the first value in the array stored with the name, as well. How is this possible?
And yes; I know there's other options for this approach, but these values are coming from Firebase (child paths) - but I just need to find a way to display the array inside the array of dictionaries.
Thanks in advance.
If you know "Hobbies" is a valid key and its dictionary value is an array of String, then you can directly access the first item in that array with:
let hobby = (array[0]["Hobbies"] as! [String])[0]
but this will crash if "Hobbies" isn't a valid key or if the value isn't [String].
A safer way to access the array would be:
if let hobbies = array[0]["Hobbies"] as? [String] {
print(hobbies[0])
}
If you use a model class/struct things get easier
Given this model struct
struct Person {
let name: String
var hobbies: [String]
}
And this dictionary
var persons = [String:Person]()
This is how you put a person into the dictionary
let andreas = Person(name: "Andreas", hobbies: ["Football", "Programming"])
persons[andreas.name] = Andreas
And this is how you do retrieve it
let aPerson = persons["Andreas"]

Swift Set to Array

An NSSet can be converted to Array using set.allObjects() but there is no such method in the new Set (introduced with Swift 1.2). It can still be done by converting Swift Set to NSSet and use the allObjects() method but that is not optimal.
You can create an array with all elements from a given Swift
Set simply with
let array = Array(someSet)
This works because Set conforms to the SequenceType protocol
and an Array can be initialized with a sequence. Example:
let mySet = Set(["a", "b", "a"]) // Set<String>
let myArray = Array(mySet) // Array<String>
print(myArray) // [b, a]
In the simplest case, with Swift 3, you can use Array's init(_:) initializer to get an Array from a Set. init(_:) has the following declaration:
init<S>(_ s: S) where S : Sequence, Element == S.Iterator.Element
Creates an array containing the elements of a sequence.
Usage:
let stringSet = Set(arrayLiteral: "car", "boat", "car", "bike", "toy")
let stringArray = Array(stringSet)
print(stringArray)
// may print ["toy", "car", "bike", "boat"]
However, if you also want to perform some operations on each element of your Set while transforming it into an Array, you can use map, flatMap, sort, filter and other functional methods provided by Collection protocol:
let stringSet = Set(["car", "boat", "bike", "toy"])
let stringArray = stringSet.sorted()
print(stringArray)
// will print ["bike", "boat", "car", "toy"]
let stringSet = Set(arrayLiteral: "car", "boat", "car", "bike", "toy")
let stringArray = stringSet.filter { $0.characters.first != "b" }
print(stringArray)
// may print ["car", "toy"]
let intSet = Set([1, 3, 5, 2])
let stringArray = intSet.flatMap { String($0) }
print(stringArray)
// may print ["5", "2", "3", "1"]
let intSet = Set([1, 3, 5, 2])
// alternative to `let intArray = Array(intSet)`
let intArray = intSet.map { $0 }
print(intArray)
// may print [5, 2, 3, 1]
I created a simple extension that gives you an unsorted Array as a property of Set in Swift 4.0.
extension Set {
var array: [Element] {
return Array(self)
}
}
If you want a sorted array, you can either add an additional computed property, or modify the existing one to suit your needs.
To use this, just call
let array = set.array
ADDITION :
Swift has no DEFINED ORDER for Set and Dictionary.For that reason you should use sorted() method to prevent from getting unexpected results such as your array can be like ["a","b"] or ["b","a"] and you do not want this.
TO FIX THIS:
FOR SETS
var example:Set = ["a","b","c"]
let makeExampleArray = [example.sorted()]
makeExampleArray
Result: ["a","b","c"]
Without sorted()
It can be:
["a","b","c"] or ["b","c","a",] or ["c","a","b"] or ["a","c","b"] or ["b","a","c"] or ["c","b","a"]
simple math : 3! = 6
The current answer for Swift 2.x and higher (from the Swift Programming Language guide on Collection Types) seems to be to either iterate over the Set entries like so:
for item in myItemSet {
...
}
Or, to use the "sorted" method:
let itemsArray = myItemSet.sorted()
It seems the Swift designers did not like allObjects as an access mechanism because Sets aren't really ordered, so they wanted to make sure you didn't get out an array without an explicit ordering applied.
If you don't want the overhead of sorting and don't care about the order, I usually use the map or flatMap methods which should be a bit quicker to extract an array:
let itemsArray = myItemSet.map { $0 }
Which will build an array of the type the Set holds, if you need it to be an array of a specific type (say, entitles from a set of managed object relations that are not declared as a typed set) you can do something like:
var itemsArray : [MyObjectType] = []
if let typedSet = myItemSet as? Set<MyObjectType> {
itemsArray = typedSet.map { $0 }
}
call this method and pass your set
func getArrayFromSet(set:NSSet)-> NSArray {
return set.map ({ String($0) })
}
Like This :
var letters:Set = Set<String>(arrayLiteral: "test","test") // your set
print(self.getArrayFromSet(letters))

Resources