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

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.

Related

Generate random Strings without a shuffle and repetition of previous ones?

My code already generates a random String of an array when I press a button, but sometimes a String gets repeated. What do I have to do so that the String "Mango" only gets called again when all the other Strings where already called without using a shuffle, I want to call one String at a time?
Example: "Mango", "Kiwi", "Banana", "Pineapple", "Melon", "Mango", "Kiwi",.....
Here is my code:
var array = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
let fruits = Int(arc4random_uniform(UInt32(array.count)))
print(array[fruits])
In order to avoid repetitions you need to keep track of which fruits have previously been seen. There are several ways to do that an all of the proposed solutions do it in one way or another.
For your specific use case, you will need this tracking to be retained outside of the code executed by the button (in your view controller for example).
Here is a generalized structure that could help with this:
(you can define it inside the view controller if this is a one-time thing or outside of it if you intend to reuse the mechanism elsewhere)
struct RandomItems
{
var items : [String]
var seen = 0
init(_ items:[String])
{ self.items = items }
mutating func next() -> String
{
let index = Int(arc4random_uniform(UInt32(items.count - seen)))
let item = items.remove(at:index)
items.append(item)
seen = (seen + 1) % items.count
return item
}
}
To use it, you would declare a variable (in your VC) that will keep track of your fruits:
var fruits = RandomItems(["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"])
And in the button's code use that variable (fruits) to print a non-repeating fruit name at each execution
func buttonPressed() // <- use your function here
{
print( fruits.next() )
}
You need to implement some logic. It's quite easy if you think harder. Run this in your Playground, or if you fully understand this block of code, you can do this in your project already.
var array = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
var selectedIndices = [Int]()
for _ in 1...20 {
let randomFruitIndex = Int(arc4random_uniform(UInt32(array.count)))
// Print only if not yet printed once
if !selectedIndices.contains(randomFruitIndex) {
print(array[randomFruitIndex])
selectedIndices.append(randomFruitIndex)
}
// Reset
if selectedIndices.count == array.count {
print("----- CLEARING SELECTED INDICES----")
selectedIndices.removeAll()
}
}
So as you can see, we are adding each generated random number (in your case, it's the fruits variable.) into an array of Int. Then if the number of selectedIndices is equal to the count of the array of fruits, clear all the stored selectedIndices.
OUTPUT:
Pineapple
Melon
Mango
Kiwi
Banana
Apple
----- CLEARING SELECTED INDICES----
Mango
Melon
This is an adaption from the accepted answer of the linked topic in my comment:
var source = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
var usedElements = [String]()
func choosePseudoRandomElement() -> String {
if source.count == 0 {
source = usedElements
usedElements = []
}
let randomIndex = Int(arc4random_uniform(UInt32(source.count)))
let randomItem = source[randomIndex]
usedElements.append(randomItem)
source.remove(at: randomIndex)
return randomItem
}
for _ in 1...18 {
print("Item: \(choosePseudoRandomElement())")
}
One potential issue with this solution is that it may happen, that the last element of one complete iteration also occurs as the first element of the second iteration. You can handle that case by comparing the randomly chosen item with the item which was chosen before (use a while loop until the items doesn't match anymore).
Also, this does remove elements from the source array. If you do not want that, create a copy of the source array.

Swift 3 - Difference between two arrays of dictionary

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.

Generating a JSON-Array and converting it to a Base64-String

I'm trying to create a Base64-String in Swift. I have an example of a Base64-encoded string and its array-counterpart. My problem now is, that I don't know how I get an equivalent array to the one which is given in the example.
Because I didn't want to mess around in my XCode-project I did the following in a playground.
given array:
{"WHERE":{"Class":"%3f","Location":"3b"},"ORDER":["Day ASC","Location DESC"]}
given Base64-string:
eyJXSEVSRSI6eyJDbGFzcyI6IiUzZiIsIkxvY2F0aW9uIjoiM2IifSwiT1JERVIiOlsiRGF5IEFTQyIsIkxvY2F0aW9uIERFU0MiXX0=
First I'm decoding the example-string
let str = "eyJXSEVSRSI6eyJDbGFzcyI6IiUzZiIsIkxvY2F0aW9uIjoiM2IifSwiT1JERVIiOlsiRGF5IEFTQyIsIkxvY2F0aW9uIERFU0MiXX0="
let data = NSData(base64EncodedString: str, options: NSDataBase64DecodingOptions(rawValue: 0))
do {
let result = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
} catch let error {
print(error)
}
//"result" is ["WHERE": ["Class": "%3f", "Location": "3b"], "ORDER": ["Day ASC", "Location DESC"]]
Below I'm trying to reproduce the string from above
var array = [String : AnyObject]()
var arrayPartA = [String : String]()
arrayPartA["Class"] = "%3f"
arrayPartA["Location"] = "3b"
array["ORDER"] = ["Day ASC", "Location DESC"]
array["WHERE"] = arrayPartA
array //The playground says that "array" is ["ORDER": ["Day ASC", "Location DESC"], "WHERE": ["Class": "%3f", "Location": "3b"]]
//"ORDER" and "WHERE" are switched but I don't get them to be at the right position
let utf8str2: NSData = String(array).dataUsingEncoding(NSUTF8StringEncoding)!
let encodedStr = utf8str2.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
//Here "encodedStr" is WyJPUkRFUiI6ICgKICAgICJEYXkgQVNDIiwKICAgICJMb2NhdGlvbiBERVNDIgopLCAiV0hFUkUiOiB7CiAgICBDbGFzcyA9ICIlM2YiOwogICAgTG9jYXRpb24gPSAzYjsKfV0=
//but it should be eyJXSEVSRSI6eyJDbGFzcyI6IiUzZiIsIkxvY2F0aW9uIjoiM2IifSwiT1JERVIiOlsiRGF5IEFTQyIsIkxvY2F0aW9uIERFU0MiXX0=
I would be glad if someone could explain to me what I'm doing wrong and how I can reproduce the given Base64-string.
Since I'm new to this website I apologize in advance for wrong layout or other possible conventions I don't know.
Could you try this please? Is this what you wanted to do? It should convert a Dictionary to base64 String
func jsonToBaseString (yourJSON: [String: String]) -> String? {
do {
let jsonData = try JSONSerialization.data(withJSONObject: yourJSON, options: JSONSerialization.WritingOptions.prettyPrinted)
return
jsonData.base64EncodedString(options: .endLineWithCarriageReturn)
} catch {
return nil
}
}
Dictionary is Swifts JSON representation...
Two important things to understand:
What you are calling an array is not an array, it's a JSON dictionary (containing an array for the ORDER key).
Be careful not to confuse the syntax of arrays and dictionaries between Swift and JSON.
In Swift, an array: [0, 1], a dictionary: ["a":0, "b":1].
In JSON, an array: [0, 1], a dictionary: {"a":0, "b":1}.
A Swift dictionary is an unordered collection. There's no "position" for key-value pairs.
You'll need to change several things:
Your input string (not serialized) is not an array, but a JSON object.
Try constructing your string with a proper JSON library, such as SwiftyJSON.
String(array) is not enough to consistently convert your objects to strings. You should use a JSON serializer (such as SwiftyJSON json.rawString()).
let follow recommendation to use some json serialization, but take in account that
{
"alfa": 1,
"beta": true
}
and
{"beta":true,"alfa":1}
represents in JSON notation the same object even though their string representation ( doesn't matter if base64 encoded or not ) are different.

How to get an array with Name stored as a string in swift

I have a program in which I have to make an string array equal to another array. The second array needs to be found by its name,
So for example something like this :
let StoreString1 = ["", ""]
let StoreString2 = ["", ""]
let FinalString = GetStringWithName("StoreString" + Number)
in C# its GetComponent("ComponentName");
Thanks for all answers, and sorry for the confusing way I wrote the question, because I didn’t really know hot to put it into words XD.
What you are trying to do can be achieved by using Dictionaries in swift. It appears that you are not familiar with this topic, so will be of no use to just throw out some code, there are many tutorials about this regard.
Just for mentioning one, (that has even images explaining how it works) you can enter here
Happy Learning & Coding! ;-)
UPDATE:
Here's a playground testing the concept: (Feel free to adjust it to your needs)
var dictionary: [String : [ String ]] = ["" : []]
let storeString = "StoreString"
func addUpdateArray(strings: [String], index: Int) {
let locator = storeString + index.description
dictionary[locator] = strings
}
func getStringWitNameIndex(index:Int) -> [String]? {
return dictionary[ storeString + index.description]
}
func addToArray(index:Int, inout destiny: [String]) {
if let array = getStringWitNameIndex(index) {
destiny = destiny + array
}
}
addUpdateArray(["Hello", "World"], 1)
addUpdateArray(["Hello", "Cats"], 3)
var finalArray : [String] = []
addToArray(1,&finalArray)
addToArray(3,&finalArray)
finalArray
In this case, finalArray ends up having: ["Hello", "World", "Hello",
"Cats"]

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