Related
I'm learning Swift and can't find the solution for my problem...
I have two Dictionaries and want to combine them:
dict1 = ["A": 1, "B": 2, "D": 5]
dict2 = ["A": 3, "C": 9, "D": 4]
The outcome should be a new Dictionary like:
dict3 = ["A": 4, "B": 2, "C": 9, "D": 9]
You can use Dictionary merging method and pass the plus sign (addition operator) as the uniquingKeysWith parameter:
let dict3 = dict1.merging(dict2, uniquingKeysWith: +) // ["A": 4, "B": 2, "D": 9, "C": 9]
I am trying to split array into pairs. I am able to split into consecutive pair but I want to split into pair which includes previous value as mentioned in Results
Logic to split in consecutive pair, which I tried.
extension Array {
func chunks(_ chunkSize: Int) -> [[Element]] {
return stride(from: 0, to: self.count, by: chunkSize).map {
Array(self[$0..<Swift.min($0 + chunkSize, self.count)])
}
}
Array:
["1", "2", "3", "4", "5", "6", "7", "8", "9"]
Results:
[["1", "2"], ["2", "3"], ["3", "4"], ["4", "5"], ["6", "7"], ["7", "8"], ["8", "9"]]
How about this:
let a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9]
let pairs = Array(zip(a, a.dropFirst())).map {[$0.0, $0.1] }
print(pairs)
That outputs
[[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9]]
Edit:
If you want arbitrary chunk-size, you could write the extension like this:
extension Array {
func chunks(_ chunkSize: Int, includePartialChunk: Bool = true) -> [[Element]] {
var indexes = Array<Int>(stride(from: 0, to: count, by: chunkSize - 1))
if includePartialChunk,
let last = indexes.last,
last < count - 1 {
indexes.append(count-1)
}
return zip(indexes, indexes.dropFirst()).map {Array(self[$0.0...$0.1])}
}
}
Use the parameter includePartialChunk to tell the function if you want to include a "partial chunk" at the end when the array size is not an even multiple of the chunk-size. If true (The default) it returns a last chunk that is smaller than chunkSize but goes to the end of the array. If false, it only returns full-sized chunks, but will skip array elements at the end that don't fit into a full-sized chunk.
(I'll have to study Leo's UnfoldSequence version and see if I can adapt my code to that.)
Not a direct answer to the question but the elements of the sequence should be computed lazily. You should use Swift UnfoldSequence type as follow:
extension Collection {
var unfoldedNeighbors: UnfoldSequence<SubSequence,Index> {
sequence(state: startIndex) { start in
guard start < endIndex else { return nil }
guard let end = index(start, offsetBy: 2, limitedBy: endIndex) else {
return nil
}
defer { formIndex(after: &start) }
return self[start..<end]
}
}
var neighborsSubsequences: [SubSequence] {
.init(unfoldedNeighbors)
}
var neighborsArrays: [[Element]] {
unfoldedNeighbors.map([Element].init)
}
}
Usage:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for neighbors in numbers.unfoldedNeighbors {
print(neighbors)
}
If you need control the number of elements of each subsequence and also if it includes the tail or not:
extension Collection {
func unfoldedNeighbors(limitedTo length: Int, includesTail: Bool = false) -> UnfoldSequence<SubSequence,Index> {
sequence(state: startIndex) { start in
guard start < endIndex else { return nil }
guard let end = index(start, offsetBy: length, limitedBy: endIndex) else {
if includesTail {
defer { formIndex(&start, offsetBy: length-1, limitedBy: endIndex) }
return self[start...]
}
return nil
}
defer { formIndex(&start, offsetBy: length-1, limitedBy: endIndex) }
return self[start..<end]
}
}
func neighborsSequences(limitedTo length: Int, includesTail: Bool = false) -> [SubSequence] {
.init(unfoldedNeighbors(limitedTo: length, includesTail: includesTail))
}
func neighborsArrays(limitedTo length: Int, includesTail: Bool = false) -> [[Element]] {
unfoldedNeighbors(limitedTo: length, includesTail: includesTail).map([Element].init)
}
}
Usage:
let numbers = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for neighbors in numbers.unfoldedNeighbors(limitedTo: 3, includesTail: true) {
print(neighbors)
}
This will print:
[1, 2, 3]
[3, 4, 5]
[5, 6, 7]
[7, 8, 9]
[9, 10]
let neighborsSequences = a.neighborsSequences(limitedTo: 3, includesTail: true) // [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [9, 10]] of type [Array<Int>.SubSequence]
let neighborsArrays = a.neighborsArrays(limitedTo: 3, includesTail: true) // [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [9, 10]] of type [[Int]]
This operation goes by windows. It does exactly what you've requested, for count 2…
import Algorithms
Array(["1", "2", "3", "4", "5", "6", "7", "8", "9"].windows(ofCount: 2))
…but it's unclear if it does what you want for other counts.
I have an array of custom objects, having ID values as properties. Now I have another array of selected objects (selected by IDs). How can I get the indices of the first array by filtering for the selected IDs (second array)?
I would like to avoid looping and think of a solution using compactMap or similar.
struct MyObject {
var ID: Int
var name: String
}
let myObjects = [
MyObject(ID: 3, name: "AAAA"),
MyObject(ID: 5, name: "BBBB"),
MyObject(ID: 12, name: "CCCC"),
MyObject(ID: 15, name: "DDDD"),
MyObject(ID: 20, name: "EEEE"),
MyObject(ID: 21, name: "GGGG"),
MyObject(ID: 22, name: "HHHH"),
MyObject(ID: 23, name: "IIII"),
]
let selectedIds = [5, 20, 23]
// How to get an array of indices for selectedIds
// Expected result [1, 4, 7]
myObjects.enumerated() // Pair-up elements and their offsets
.filter { selectedIds.contains($0.element.ID) } // Get the ones you want
.map { $0.offset } // Extract the offsets
You could filter the indices
let selectedIndices = myObjects.indices.filter{selectedIds.contains(myObjects[$0].ID)}
I have an array ([Dictionary<String, String>]) of dictionary, say,
let dict0 = ["0": 1, "1": 2, "2": 4]
let dict1 = ["0": 5, "1": 4, "2": 8]
let dict2 = ["0": 3, "1": 9, "2": 7]
let array = [dict0, dict1, dict2]
So it looks like the following.
[
["0": 1, "1": 2, "2": 4],
["2": 8, "0": 5, "1": 4],
["2": 7, "1": 9, "0": 3]
]
Let me assume that I have an array ([String]) of keys like
let keys = ["ant", "bee", "spider"]
Is there a simple way of changing my array's keys such that it will look like the following?
[
["ant": 1, "bee": 2, "spider": 4],
["spider": 8, "ant": 5, "bee": 4],
["spider": 7, "bee": 9, "ant": 3]
]
Thanks.
Don't actually know what you mean by simple way but the following will get the job done.
let dict0 = ["0": 1, "1": 2, "2": 4]
let dict1 = ["0": 5, "1": 4, "2": 8]
let dict2 = ["0": 3, "1": 9, "2": 7]
let array = [dict0, dict1, dict2]
let keys = ["ant", "bee", "spider"]
let keysIndexedDictionary = Dictionary(uniqueKeysWithValues: keys.enumerated().map { (String($0), $1) })
let mappedArray = array.map { dic -> [String : Int] in
// I wish this can be shortened using $ notation
let mappedTuples = dic.flatMap { [(keysIndexedDictionary[$0] ?? $0) : $1] }
return Dictionary(uniqueKeysWithValues: mappedTuples)
}
I have multiple array (max 15), each can max have 1800 objects.
I need to combine them into a single array, so that I then can apply a delimiter (',') to generate a csv file. The problem is when I combine them into a single array, it has to be sequentially combined, like first objects of each array should be inserted initially followed by the 2nd index of objects then 3rd and so on.
I'm able to achieve the result I want by using a for-in loop. However this doesn't very swifty. I feel it can be done in a much cleaner way using functional methods available in swift (using map, reduce and filter functions).
However I'm not able to combine them perfectly. Can anyone help me with using the swift functional methods to achieve the result.
P.S: Let me know if you want me to post the for-in loop code, but I believe that's not required.
Given 4 (or more) arrays
let list0: [Int] = [ 1, 2, 3, 6, 7, 8, 9 ]
let list1: [Int] = [ 10, 20, 30, 40, 50, 60, 70, 80, 90]
let list2: [Int] = [ 100, 200, 300, 400, 500, 600, 700, 800, 900]
let list3: [Int] = [ 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000 ]
enumerate each one and put them into another array
let lists = [
list0.enumerate().map { (index: $0, array: 0, value: $1) },
list1.enumerate().map { (index: $0, array: 1, value: $1) },
list2.enumerate().map { (index: $0, array: 2, value: $1) },
list3.enumerate().map { (index: $0, array: 3, value: $1) }
]
Now you can write
let sorted = lists
.flatten()
.sort { ($0.index, $0.array) < ($1.index, $1.array) }
.map { $0.value }
[1, 10, 100, 1000, 2, 20, 200, 2000, 3, 30, 300, 3000, 6, 40, 400, 4000, 7, 50, 500, 5000, 8, 60, 600, 6000, 9, 70, 700, 7000, 80, 800, 8000, 90, 900, 9000]
I would consider making this an extension for arrays of arrays (although note you cannot do this directly, see this Q&A). You could then use a combination of reduce(_:_:) along with both flavours of flatMap(_:) in order to sequentially merge your arrays by iterating through the lengths of the inner collections and extracting the elements at each given index.
extension Array where Element : RandomAccessCollection, Element.Index == Int, Element.IndexDistance == Element.Index {
func joinedByTransposing() -> [Element.Iterator.Element] {
// The maximum length of the inner collections. Obviously if the 2D array is
// guaranteed to be n*m, you can optimise by just taking the first inner
// collection's count (and obviously you'll want to check that the array isn't empty first).
let maxIndex = self.reduce(0, {$0 > $1.count ? $0 : $1.count})
// Iterates through the max length of the inner collections, joining the restantant collections
// from the transform below into a single array.
return (0..<maxIndex).flatMap { index in
// Iterate through each inner collection, getting the element at the current index of iteration,
// or returning nil if the index is out of bounds. This flatMap will filter out any nils.
// If the 2D array is guarenteed to be n*m, this can be replaced by self.map { $0[index] }
self.flatMap { innerArray in
// Simple bounds check to get the element at the given index, or nil if out of bounds
index < innerArray.count ? innerArray[index] : nil
}
}
}
}
let array0 = [1, 2, 3, 4 ]
let array1 = [10, 20, 30 ]
let array2 = [100, 200, 300, 6, 7]
let result = [array0, array1, array2].joinedByTransposing()
print(result)
// [1, 10, 100, 2, 20, 200, 3, 30, 300, 4, 6, 7]
It’s worth noting that this solution has an overall time complexity of O(n * m) – whereas solutions that utilise sorted(by:) will have a time complexity of at least O(n * m * log(n * m)). For large arrays, this extra cost may well be non-trivial.
Here is my functional approach to the problem that you describe in your post.
First I flatted the array using the enumerated method, which return a tuple with the element position inside the array and It's value.
After this, You have an array with this tuples, next step, sort this big array by the offset (position) value of each element.
Once the array is sorted you hace to extract the value with map function.
And final step, once we have an array with sorted values, You have to reduce it to a string with the reduce function
// A group of arrays
var array1: [Int] = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
var array2: [Int] = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
var array3: [Int] = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
var array4: [Int] = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
// This is **your** array
let bigOne = [ array1, array2, array3, array4 ]
// And here is the functional concatenation.
let flattedOne = bigOne.flatMap({ $0.enumerated() }).sorted(by: { $0.0 < $0.1 }).map({$0.element}).reduce("")
{
return $0.isEmpty ? "\($1)" : "\($0), \($1)"
}
print(flattedOne)
Here's another approach...
public func sort(compound array: [[Int]]) -> [Int]
{
let max_index: Int = array.map({ $0.count }).max()!
var sorted: [Int] = [Int]()
(0 ..< max_index).forEach({ index in
array.forEach()
{
if $0.count > index
{
sorted.append($0[index])
}
}
})
return sorted
}
// A group of arrays
var array1: [Int] = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 111, 112 ]
var array2: [Int] = [ 10, 20, 3, 4, 5, 6, 7, 8, 9, 10 ]
var array3: [Int] = [ 1000, 2000, 3, 4, 5, 600, 7, 8, 9, 10, 11 ]
var array4: [Int] = [ 100, 200, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 ]
let bigOne: [[Int]] = [ array1, array2, array3, array4 ]
let sorted: [Int] = sort(compound: bigOne)
print(sorted)
And if you want the array as a CSV string...
print(sorted.reduce("") { return $0.isEmpty ? "\($1)" : "\($0), \($1)" })