Most efficient way to flatten an array of dictionaries containing arrays - swift

Let's say I have an array of dictionaries, and each contains an array of letters. Like this:
let dicts = [["letters" : ["a","b","c"]],
["letters" : ["d","e","f"]]]
What is the most efficient way to create a flattened array of all the letters from all the dictionaries?

You can use reduce(_:_:) for that.
let array = dicts.reduce([]) { $0 + ($1["letters"] ?? []) }
print(array) // ["a","b","c","d","e","f"]
Edit: As #Hamish suggested a link in comment simplest solution is having less time, so if you are having large amount of data then you can use forEach closure with array.
var result = [String]()
dicts.forEach {
result.append(contentsOf: $0["letters"] ?? [])
}

You can use flatMap() to map each dictionary to the corresponding
letters array and join the results:
let dicts = [["letters" : ["a","b","c"]],
["letters" : ["d","e","f"]]]
let letters = Array(dicts.flatMap { $0["letters"] }.joined())
print(letters) // ["a", "b", "c", "d", "e", "f"]
This is effective insofar as no additional intermediate arrays are
created during the join. As #Hamish pointed out below, the
intermediate [[String]] can be avoided with
let letters = Array(dicts.lazy.flatMap { $0["letters"] }.joined())

You can use any one of these. You don't need to specify the key name of the dictionary.
Solution 1
let array = dicts.reduce([]) { $0 + ($1.values.reduce([]) { $0 + $1 }) }
print(array) // ["a","b","c","d","e","f"]
Solution 2
let array = dicts.flatMap { $0.values.joined() }
print(array) // ["a","b","c","d","e","f"]

Related

Get all values into dictionary and create a String with a specific format

I have a dictionary with this structure:
a: [1,2]
b: [3,4]
c: [5,6]
and I need to return a string with this structure.
a,b,c\n1,3,5\n2,4,6
I solved the first part of the string. But to get the rest of the String. I try to iterate into my dictionary to get the first elements for each key in my dictionary and then get the rest for each value into the array.
Is there an easier way to get this?
Once you know what's the order of the keys (alpha ?), you can use this:
let dict: [String: [Int]] = ["a": [1,2], "b": [3, 4], "c": [5, 6]]
let keys = dict.keys.sorted() //Or do whatever you want here to get your target order
var matrix: [[String]] = []
keys.forEach {
guard let arrayAsInt = dict[$0] else { return }
let arrayAsString = arrayAsInt.map{ "\($0)" }
matrix.append( [$0] + arrayAsString)
}
print("Matrix: \(matrix)")
let transposed = matrix.transposed()
print("Transposed Matrix: \(transposed)")
let output = transposed.map { $0.joined(separator: ",")}.joined(separator: "\n")
print(output)
The outputs:
$>Matrix: [["a", "1", "2"], ["b", "3", "4"], ["c", "5", "6"]]
$>Transposed Matrix: [["a", "b", "c"], ["1", "3", "5"], ["2", "4", "6"]]
$>a,b,c
1,3,5
2,4,6
Obvisouly the "\n" might be invisible and be an actual new line
a,b,c
1,3,5
2,4,6
Being
a,b,c\n1,3,5\n2,4,6
What's the idea behind that? Create a matrix and use the transpose (it's used in maths with matrix, it's one of the basic modification of a matrix).
First transform the [String: [Int]] into a [[String]], where each element would be key followed by its values. I transformed it there as String for simpler code later.
Why doing that? Because the matrix value is easy to get from your initial dict. the transposed value is harder (not impossible) to get from dict but easier from matrix, and the transposed is quickly transformed into your format.
So my thinking was the reverse:
Get a structure from your output, then how to get it, it's a transpose, so I need to get the initial input as it, etc.
With the help of a code for Transpose Matrix (that accept String elements).
extension Collection where Self.Iterator.Element: RandomAccessCollection {
// PRECONDITION: `self` must be rectangular, i.e. every row has equal size.
func transposed() -> [[Self.Iterator.Element.Iterator.Element]] {
guard let firstRow = self.first else { return [] }
return firstRow.indices.map { index in
self.map{ $0[index] }
}
}
}
Any code (there a various) working ones, should the trick. I took it from here.
As pointed by #Leo Dabus, you can remove the Self.Iterator.Element
from the extension code (twice). I just wanted to it as such, not modifying the initial answer since it's not mind.
What you are looking for, besides composing the final string, is how to transpose a collection (this would work with collections of different sizes as well):
extension Sequence {
func max<T: Comparable>(_ predicate: (Element) -> T) -> Element? {
self.max(by: { predicate($0) < predicate($1) })
}
}
extension Collection where Element: RandomAccessCollection, Element.Indices == Range<Int> {
func transposed() -> [[Element.Element]] {
(0..<(max(\.count)?.count ?? .zero)).map {
index in compactMap { $0.indices ~= index ? $0[index] : nil }
}
}
}
let dict = ["a": [1,2,3],
"b": [4,5,6],
"c": [7,8,9]]
let sorted = dict.sorted(by: {$0.key < $1.key})
let result = sorted.map(\.key).joined(separator: ",") + "\n" +
sorted.map(\.value).transposed().map {
$0.map(String.init).joined(separator: ",")
}.joined(separator: "\n")
result // "a,b,c\n1,4,7\n2,5,8\n3,6,9"
A dictionary is an unordered collection so you need to sort it according to any specific key. Here I sort the dictionary according to the key if you don't care about an order you can just remove sort.
let dict: [String: Any] = ["a": [1,2], "b": [3,4], "c": [5,6]]
let sortedKey = dict.keys.sorted(by: <)
let key = sortedKey.joined(separator: ",")
var firstValues: [String] = []
var secondValues: [String] = []
sortedKey.forEach { (key) in
if let dictValue = dict[key] as? [Int],
let firstValue = dictValue.first,
let secondValue = dictValue.last {
firstValues.append("\(firstValue)")
secondValues.append("\(secondValue)")
}
}
let finalString = key + "\n" + firstValues.joined(separator: ",") + "\n" + secondValues.joined(separator: ",")
print(finalString) // "a,b,c\n1,3,5\n2,4,6"

Basic Dictionary Operations in Swift [duplicate]

I'm trying to figure out the best way in Swift to add values to an Array that is a Value in a Dictionary. I want to build a dictionary of contacts sorted by the first letter of their first name. For example [A : [Aaron, Adam, etc...], B : [Brian, Brittany, ect...], ...]
I found this function:
updateValue(_:forKey:)
And tried using it in a loop:
for contact in self.contacts.sorted() {
self.contactDictionary.updateValue([contact], forKey: String(describing: contact.characters.first))
}
But when I tried to use that it replaced the existing array with a new one. I know I can manually check to see if the key in the dictionary exists, if it does, retrieve the array and then append a new value, otherwise add the new key/value pair but I'm not sure if Swift provides an easier/better way to do this.
Any insight would be much appreciated!
You can use reduce(into:) method (Swift4) and as follow:
let contacts = ["Aaron", "Adam", "Brian", "Brittany", ""]
let dictionary = contacts.reduce(into: [String:[String]]()) { result, element in
// make sure there is at least one letter in your string else return
guard let first = element.first else { return }
// create a string with that initial
let initial = String(first)
// initialize an array with one element or add another element to the existing value
result[initial] = (result[initial] ?? []) + [element]
}
print(dictionary) // ["B": ["Brian", "Brittany"], "A": ["Aaron", "Adam"]]
If you are using Swift3 or earlier you would need to create a mutable result dictionary inside the closure:
let contacts = ["Aaron", "Adam", "Brian", "Brittany", ""]
let dictionary = contacts.reduce([String:[String]]()) { result, element in
var result = result
guard let first = element.first else { return result }
let initial = String(first)
result[initial] = (result[initial] ?? []) + [element]
return result
}
print(dictionary) // ["B": ["Brian", "Brittany"], "A": ["Aaron", "Adam"]]
Note that the result is not sorted. A dictionary is an unordered collection. If you need to sort your dictionary and return an array of (key, Value) tuples you can use sorted by key as follow:
let sorted = dictionary.sorted {$0.key < $1.key}
print(sorted)
"[(key: "A", value: ["Aaron", "Adam"]), (key: "B", value: ["Brian", "Brittany"])]\n"
Swift 4's new dictionary initializers can do it all for you:
let contactInitials = contacts.filter{!$0.isEmpty}.map{ ($0.first!,[$0]) }
let dict = [Character:[String]](contactInitials, uniquingKeysWith:+)

Adding items to Array as a Dictionary Value

I'm trying to figure out the best way in Swift to add values to an Array that is a Value in a Dictionary. I want to build a dictionary of contacts sorted by the first letter of their first name. For example [A : [Aaron, Adam, etc...], B : [Brian, Brittany, ect...], ...]
I found this function:
updateValue(_:forKey:)
And tried using it in a loop:
for contact in self.contacts.sorted() {
self.contactDictionary.updateValue([contact], forKey: String(describing: contact.characters.first))
}
But when I tried to use that it replaced the existing array with a new one. I know I can manually check to see if the key in the dictionary exists, if it does, retrieve the array and then append a new value, otherwise add the new key/value pair but I'm not sure if Swift provides an easier/better way to do this.
Any insight would be much appreciated!
You can use reduce(into:) method (Swift4) and as follow:
let contacts = ["Aaron", "Adam", "Brian", "Brittany", ""]
let dictionary = contacts.reduce(into: [String:[String]]()) { result, element in
// make sure there is at least one letter in your string else return
guard let first = element.first else { return }
// create a string with that initial
let initial = String(first)
// initialize an array with one element or add another element to the existing value
result[initial] = (result[initial] ?? []) + [element]
}
print(dictionary) // ["B": ["Brian", "Brittany"], "A": ["Aaron", "Adam"]]
If you are using Swift3 or earlier you would need to create a mutable result dictionary inside the closure:
let contacts = ["Aaron", "Adam", "Brian", "Brittany", ""]
let dictionary = contacts.reduce([String:[String]]()) { result, element in
var result = result
guard let first = element.first else { return result }
let initial = String(first)
result[initial] = (result[initial] ?? []) + [element]
return result
}
print(dictionary) // ["B": ["Brian", "Brittany"], "A": ["Aaron", "Adam"]]
Note that the result is not sorted. A dictionary is an unordered collection. If you need to sort your dictionary and return an array of (key, Value) tuples you can use sorted by key as follow:
let sorted = dictionary.sorted {$0.key < $1.key}
print(sorted)
"[(key: "A", value: ["Aaron", "Adam"]), (key: "B", value: ["Brian", "Brittany"])]\n"
Swift 4's new dictionary initializers can do it all for you:
let contactInitials = contacts.filter{!$0.isEmpty}.map{ ($0.first!,[$0]) }
let dict = [Character:[String]](contactInitials, uniquingKeysWith:+)

Randomize two arrays the same way Swift

I know there is a new shuffle method with iOS 9
but I am wondering if there is anyway to shuffle two arrays the same way?
For example
[1,2,3,4] and [a,b,c,d]
shuffle
[3,4,1,2] and [c,d,a,b]
Using the shuffle() method from How do I shuffle an array in Swift? and the ideas from How can I sort multiple arrays based on the sorted order of another array
you can shuffle the array indices and then re-order both (or more)
arrays accordingly:
let a = [1, 2, 3, 4]
let b = ["a", "b", "c", "d"]
var shuffled_indices = a.indices.shuffle()
let shuffled_a = Array(PermutationGenerator(elements: a, indices: shuffled_indices))
let shuffled_b = Array(PermutationGenerator(elements: b, indices: shuffled_indices))
print(shuffled_a) // [3, 1, 2, 4]
print(shuffled_b) // ["c", "a", "b", "d"]
Update for Swift 3 (Xcode 8): PermutationGenerator does not
exist in Swift 3 anymore.
Using the shuffled() method
from Shuffle array swift 3 the same can be achieved with
var shuffled_indices = a.indices.shuffled()
let shuffled_a = shuffled_indices.map { a[$0] }
let shuffled_b = shuffled_indices.map { b[$0] }
Use a dictionary to store the values temporarily, shuffle the keys and then rebuild the other array by extracting the values from the dictionary.
I'm unaware of any built-in shuffle mechanism in Swift 2.0. Assuming this doesn't exist, I borrowed some code from here.
extension CollectionType where Index == Int {
/// Return a copy of `self` with its elements shuffled
func shuffle() -> [Generator.Element] {
var list = Array(self)
list.shuffleInPlace()
return list
}
}
extension MutableCollectionType where Index == Int {
/// Shuffle the elements of `self` in-place.
mutating func shuffleInPlace() {
// empty and single-element collections don't shuffle
if count < 2 { return }
for i in 0..<count - 1 {
let j = Int(arc4random_uniform(UInt32(count - i))) + i
guard i != j else { continue }
swap(&self[i], &self[j])
}
}
}
let shuffleOrder = [0,1,2,3]
let shuffled = shuffleOrder.shuffle()
var newArray1 = [String]()
var newArray2 = [String]()
let array1 = ["a", "b", "c", "d"]
let array2 = ["w", "x", "y", "z"]
shuffled.forEach() { index in
newArray1.append(array1[index])
newArray2.append(array2[index])
}
This solves the problem in a really straight forward way. It creates an array, shuffleOrder, that just has an index for each possible index in the starting arrays. It then shuffles these indices to create a random sampling order. Finally, it constructs two new arrays, based off of the starting arrays, sampling them with the shuffled values. While this doesn't mutate the original 2 arrays in place, it would be simple to modify this to do so.
Based upon Martin R's original answer, you could approach the problem using GameKit.
The answer is written in Swift4:
var arrayA = [1, 2, 3, 4]
var arrayB = ["a", "b", "c", "d"]
//Get The Indices Of The 1st Array
var shuffledIndices: [Int] = Array(arrayA.indices)
print("Shuffled Indices = \(shuffledIndices)")
//Shuffle These Using GameKit
shuffledIndices = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: shuffledIndices) as! [Int]
//Map The Objects To The Shuffled Indices
arrayA = shuffledIndices.map { arrayA[$0] }
arrayB = shuffledIndices.map { arrayB[$0] }
//Log The Results
print("""
Array A = \(arrayA)
Array B = \(arrayB)
""")
Hope it helps ^_________^.

How to remove an element of a given value from an array in Swift

I want to remove all elements of value x from an array that contains x, y and z elements
let arr = ['a', 'b', 'c', 'b']
How can I remove all elements of value 'b' from arr?
A filter:
let farray = arr.filter {$0 != "b"}
If you need to modify initial array, you can use the function removeAll(where:) that is available in Swift 4.2/Xcode 10:
var arr = ["a", "b", "c", "b"]
arr.removeAll(where: { $0 == "b" })
print(arr) // output is ["a", "c"]
However, if you are using Xcode 9 you can find this function in Xcode9to10Preparation (this library provides implementations of some new functions from Xcode 10).
var array : [String]
array = ["one","two","one"]
let itemToRemove = "one"
while array.contains(itemToRemove) {
if let itemToRemoveIndex = array.index(of: itemToRemove) {
array.remove(at: itemToRemoveIndex)
}
}
print(array)
Works on Swift 3.0.
EDITED according to comments:
I like this approach:
var arr = ["a", "b", "c", "b"]
while let idx = arr.index(of:"b") {
arr.remove(at: idx)
}
Original answer (before editing):
let arr = ['a', 'b', 'c', 'b']
if let idx = arr.index(of:"b") {
arr.remove(at: idx)
}
In Swift 3 I simply do:
arr = arr.filter { $0 != "a" }
.filter, .sort and .map are great for saving time and solve lots of problems with little code.
This article has good examples and explain the differences and how they work: https://useyourloaf.com/blog/swift-guide-to-map-filter-reduce/
If you have more than one element to remove, thanks to first answer.
var mainArray = ["a", "b", "qw", "qe"]
let thingsToRemoveArray = ["qw", "b"]
for k in thingsToRemoveArray {
mainArray = mainArray.filter {$0 != k}
}
A general approach is to exploit first class procedures. (However, this approach is much more powerful than what is required for your question.) To illustrate, say you want to avoid "Justin" repeatedly in many collections.
let avoidJustin = notEqualTester ("Justin")
let arrayOfUsers = // ...
arrayOfUsers.filter (avoidJustin)
let arrayOfFriends = // ...
arrayOfFriends.filter (avoidJustin)
With this, you avoid repeatedly creating a closure each time you want to avoid Justin. Here is notEqualTester which, given a that, returns a function of this that returns this != that.
func notEqualTester<T: Equatable> (that:T) -> ((this:T) -> Bool) {
return { (this:T) -> Bool in return this != that }
}
The returned closure for this captures the value for that - which can be useful when that is no longer available.
If you only have one element of that value, this would probably be the easiest.
arr.remove(at: arr.index(of: ‘b’))