I am learning Quick Sort in swift and need to compose a complicated array.
Here is the code:
var arrayOne = 1...500
var arrayTwo = 501...1000
var array_one = arrayOne.reversed()
var array_two = arrayTwo.reversed()
var array = arrayOne + arrayTwo
I want to combine arrayOne + arrayTwo to array.
I can not use the + operator, Xcode tips me
Binary operator '+' cannot be applied to two
'CountableClosedRange' operands
I know how to get it by using for loops.
Elegant way is really needed. Such as Higher order function.
1...500 is a range and (1...500).reversed() is a collection. Both are sequences so that you can append them to an array:
let rangeOne = 1...500
let rangeTwo = 501...1000
let array = [] + rangeOne.reversed() + rangeTwo.reversed()
// [500, 499, ..., 2, 1, 1000, 999, ..., 502, 501]
Alternative solutions are:
let array = Array(rangeOne.reversed()) + rangeTwo.reversed()
let array = Array([rangeOne.reversed(), rangeTwo.reversed()].joined())
let array = Array(rangeOne.reversed()) + Array(rangeTwo.reversed())
let array = [rangeOne.reversed(), rangeTwo.reversed()].flatMap { $0 }
Related
I have a text divided into an array of strings where the user can tap on each word, adding the word's index (key) and the string (value) to a dictionary.
Now, if the user adds two or more words that are adjacent, I would like the concatenate the string and make them share one index.
My idea was to use a computed property that rearranges the array of strings based on the keys and values in the dictionary. So when the user taps on a word, the function should update the dictionary while checking if there are any adjacent indexes already added.
Example code:
let text = "This is a test for merging adjacent words that the user has selected."
//The text divided in separate words that can be tapped
var arrayOfString: [String] {
text.components(separatedBy: " ")
}
//If a user taps on a word it will be saved with its index
var userSelectedWords: [Int:String] = [2 : "a", 3 : "test", 4 : "for", 6 : "adjacent", 7 : "words", 9 : "the", 11 : "has"]
//Mapping all the keys into an array
var selectedKeys = userSelectedWords.map { $0.key }.sorted()
var indexToRemove = [Int]()
for i in 0..<selectedKeys - 1 {
//If the key has a value of one less that the succeding key, the words are adjacent
if selectedKeys[i] == selectedKeys[i + 1] - 1 {
indexToRemove.append(selectedKeys[i+1])
if let currentWord = userSelectedWords[selectedKeys[i]], let nextWord = userSelectedWords[selectedKeys[i + 1]] {
concatenatedString.append("\(currentWord) \(nextWord)")
}
}
}
print(indexToRemove)
//Prints: [3, 4, 7] which are the indexes that should be removed.
print(concatenatedString)
//Prints: ["a test", "test for", "adjacent words"]
/*
Here I'm stuck. If there are more than two words adjacent, the function will of
course continue the iteration and create a new item in the concatenatedString.
It feels like it starts to get way too complicated.
*/
I'd very much appreciate any input or help in this regard. Maybe I'm just looking at it the wrong way...
A functional approach would be to create an array of Range objects based upon the contiguous sorted keys, and then filter for those whose length was greater than 1
let text = "This is a test for merging adjacent words that the user has selected."
let allWords = text.components(separatedBy: " ")
let userSelectedWords = [2 : "a", 3 : "test", 4 : "for", 6 : "adjacent", 7 : "words", 9 : "the", 11 : "has"]
let result = userSelectedWords.keys
.sorted()
.reduce(into: [Range]()) { ranges, index in
if let range = ranges.last, range.endIndex == index {
ranges[ranges.count - 1] = range.startIndex ..< index + 1
} else {
ranges.append(index ..< index + 1)
}
}
.filter { $0.count > 1 }
.map { allWords[$0].joined(separator: " ") }
print(result) // ["a test for", "adjacent words"]
I have an array of SCNNode that are sorted by their y positions(Float):
nodesSortedByY = scene.rootNode.childNodes.sorted { $0.position.y > $1.position.y }
What I would like to do is get a new array from nodesSortedByY where the y values are within a certain range in a similar way to how subscript works but by passing actual values not indexes.
For example:
let nodesSortedByY = [5.0, 4.0, 4.0, 3.0, 2.0, 2.0, 1.0]
let subRange = nodesSortedByY(4.0...2.0)
print(subRange) // [4.0, 4.0, 3.0, 2.0, 2.0]
I tried using indexes originally combined with this binary search but it doesnt work if the values dont exist within the array:
let yPositions = nodesSortedByY.map({ $0.position.y })
let firstIndex = yPositions.binarySearch(forFirstIndexOf: firstValue) ?? 0
let lastIndex = yPositions.binarySearch(forLastIndexOf: lastValue) ?? 0
nodesSortedByY[lastIndex...firstIndex]
What you want is to filter().
let sub = nodesSortedByY.filter { (2.0...4.0).contains($0.position.y) }
We keep only the elements in nodesSortedByY where its y position is inside the range [2.0; 4.0].
Since you sorted your array (descending order), you can applied that logic too (modification of your attempt)
let lowerBound = nodesSortedByY.firstIndex(where: { $0 <= 4.0 }) ?? nodesSortedByY.startIndex
let upperBound = nodesSortedByY.lastIndex(where: { $0 >= 2.0 }) ?? nodesSortedByY.endIndex
let sub = nodesSortedByY[lowerBound...upperBound]
There must be some really elegant way of copying end of the Array using Swift starting from some index, but I just could not find it, so I ended with this:
func getEndOfArray<T>( arr : [T], fromIndex : Int? = 0) -> [T] {
var i=0;
var newArray : [T] = [T]()
for item in arr {
if i >= fromIndex {
newArray.append(item)
}
i = i + 1;
}
return newArray // returns copy of the array starting from index fromIndex
}
Is there a better way of doing this without extra function?
And another one ...
let array = [1, 2, 3, 4, 5]
let fromIndex = 2
let endOfArray = array.dropFirst(fromIndex)
print(endOfArray) // [3, 4, 5]
This gives an ArraySlice which should be good enough for most
purposes. If you need a real Array, use
let endOfArray = Array(array.dropFirst(fromIndex))
An empty array/slice is created if the start index is larger than (or equal to) the element count.
You can use suffix:
let array = [1,2,3,4,5,6]
let lastTwo = array.suffix(2) // [5, 6]
As mentioned in the comment: This gives you an ArraySlice object which is sufficient for most cases. If you really need an Array object you have to cast it:
let lastTwoArray = Array(lastTwo)
You can subscript an open ended range now. This will include elements starting at index 5.
Swift 4.2
array[5...]
This is one possible solution, there are probably a few more
let array = [1, 2, 3, 4, 5]
let endOfArray = Array(array[2..<array.endIndex]) // [3, 4, 5]
Or with dynamic index and range check
let array = [1, 2, 3, 4, 5]
let index = 2
let endOfArray : [Int]
if index < array.count {
endOfArray = Array(array[index..<array.endIndex]) // [3, 4, 5]
} else {
endOfArray = array
}
The re-initializition of the array is needed since the range subscription of Array returns ArraySlice<Element>
You could use the new method removeFirst(_:) in Swift 4
var array = [1, 2, 3, 4, 5]
array.removeFirst(2)
print(array) // [3, 4, 5]
You could use filter
func getEndOfArray( arr : [Int], fromIndex : Int? = 0) -> [Int] {
let filteredArray = arr.filter({$0 <= fromIndex})
return filteredArray
}
took the liberty of changing to [Int] for the sake of this example.
I'm using vDSP_conv to perform autocorrelation. Mostly it works just fine but every so often it's filling the output array with NaNs.
The code:
func corr_test() {
var pass = 0
var x = [Float]()
for i in 0..<2000 {
x.append(Float(i))
}
while true {
print("pass \(pass)")
let corr = autocorr(x)
if corr[1].isNaN {
print("!!!")
}
pass += 1
}
}
func autocorr(a: [Float]) -> [Float] {
let resultLen = a.count * 2 + 1
let padding = [Float].init(count: a.count, repeatedValue: 0.0)
let a_pad = padding + a + padding
var result = [Float].init(count: resultLen, repeatedValue: 0.0)
vDSP_conv(a_pad, 1, a_pad, 1, &result, 1, UInt(resultLen), UInt(a_pad.count))
return result
}
The output:
pass ...
pass 169
pass 170
pass 171
(lldb) p corr
([Float]) $R0 = 4001 values {
[0] = 2.66466637E+9
[1] = NaN
[2] = NaN
[3] = NaN
[4] = NaN
...
I'm not sure what's going on here. I think I'm handling the 0 padding correctly since if I weren't I don't think I'd be getting correct results 99% of the time.
Ideas? Gracias.
Figured it out. The key was this comment from https://developer.apple.com/library/mac/samplecode/vDSPExamples/Listings/DemonstrateConvolution_c.html :
// “The signal length is padded a bit. This length is not actually passed to the vDSP_conv routine; it is the number of elements
// that the signal array must contain. The SignalLength defined below is used to allocate space, and it is the filter length
// rounded up to a multiple of four elements and added to the result length. The extra elements give the vDSP_conv routine
// leeway to perform vector-load instructions, which load multiple elements even if they are not all used. If the caller did not
// guarantee that memory beyond the values used in the signal array were accessible, a memory access violation might result.”
“Padded a bit.” Thanks for being so specific. Anyway here's the final working product:
func autocorr(a: [Float]) -> [Float] {
let filterLen = a.count
let resultLen = filterLen * 2 - 1
let signalLen = ((filterLen + 3) & 0xFFFFFFFC) + resultLen
let padding1 = [Float].init(count: a.count - 1, repeatedValue: 0.0)
let padding2 = [Float].init(count: (signalLen - padding1.count - a.count), repeatedValue: 0.0)
let signal = padding1 + a + padding2
var result = [Float].init(count: resultLen, repeatedValue: 0.0)
vDSP_conv(signal, 1, a, 1, &result, 1, UInt(resultLen), UInt(filterLen))
// Remove the first n-1 values which are just mirrored from the end so that [0] always has the autocorrelation.
result.removeFirst(filterLen - 1)
return result
}
Note that the results here aren't normalized.
In my app, I have 2 arrays which are in use a lot, an array that stores score values as an Integer, and another array that stores dates in the format "mm/dd/yy". These arrays are continuously being appended, and the indexes of these arrays correspond to each other, for example, index 0 of dates array corresponds to index 0 of score array. I want these arrays to be turned into a dictionary upon when a second screen loads(these are global variables). For example, these are the type of values in each array.
score == [1,2,3,4,5,6,7,8,9]
dates == ["7/12/15","7/12/15","7/12/15","7/12/15","7/13/15","7/13/15","7/13/15","7/13/15"," 7/14/15"]
What I want to happen, is that upon viewDidLoad(), this gets created.
var scoreDatesDictionary = [
"7/12/15": [1,2,3,4]
"7/13/15": [5,6,7,8]
"7/14/15": [9]
]
In its essence, the two arrays have corresponding values, (firstArray[0]) corresponds to (secondArray[0]). I am trying to make it that in the secondArray(dates), identical strings get matched up in a dictionary with their corresponding index values. I may not make much sense, but the sample code I provided should work. I spent a lot of time working with this, and I can't find a solution.
let score = [1,2,3,4,5,6,7,8,9,]
let dates = ["7/12/15","7/12/15","7/12/15","7/12/15","7/13/15","7/13/15","7/13/15","7/13/15"," 7/14/15"]
var dic = [String:[Int]]()
for var index=0;index < dates.count; index++ {
let key = dates[index];
var value = dic[key]
if value == nil{
dic[key] = [score[index]]
}else{
value!.append(score[index])
dic[key] = value
}
}
println(dic)
This will log
[7/12/15: [1, 2, 3, 4], 7/14/15: [9], 7/13/15: [5, 6, 7, 8]]
func zipToDict<
S0 : SequenceType,
S1 : SequenceType where
S0.Generator.Element : Hashable
>(keys: S0, values: S1) -> [S0.Generator.Element:[S1.Generator.Element]] {
var dict: [S0.Generator.Element:[S1.Generator.Element]] = [:]
for (key, value) in zip(keys, values) {
dict[key] = (dict[key] ?? []) + [value]
}
return dict
}
let score = [1,2,3,4,5,6,7,8,9]
let dates = ["7/12/15","7/12/15","7/12/15","7/12/15","7/13/15","7/13/15","7/13/15","7/13/15"," 7/14/15"]
print(zipToDict(dates, score)) // [7/12/15: [1, 2, 3, 4], 7/14/15: [9], 7/13/15: [5, 6, 7, 8]]
Working off #Leo's correct answer, here's a version that makes use of enumerate and the nil coalescing operator ?? to do this more cleanly:
For Swift 1.2:
let score = [1,2,3,4,5,6,7,8,9]
let dates = ["7/12/15","7/12/15","7/12/15","7/12/15","7/13/15","7/13/15","7/13/15","7/13/15"," 7/14/15"]
var dic = [String:[Int]]()
for (index, date) in enumerate(dates) {
dic[date] = (dic[date] ?? []) + [score[index]]
}
print(dic) // prints "[7/12/15: [1, 2, 3, 4], 7/14/15: [9], 7/13/15: [5, 6, 7, 8]]"
For Swift 2.0, you need to use dates.enumerate() instead.