Related
I have a [Double] with values like [60, 21, -18, -57, -95, -67, -29, 8, 45, 82].
Is there a quick way with Swift to retrieve the numbers before the next element is a number with the opposite sign?
With the example data set, the returned values should be: -18, 21 and -29, 8 or their equivalent indexes in the array.
I know I can use .filter() to compare values and sort them to retrieve the max and min of the entire set, but retrieving multiple ranges dependent on sign changes (can be more than two) is a bit more confusing for me.
I would do it like this:
let highestNegative = values.filter { $0 < 0 }.max()
let lowestPositive = values.filter { $0 > 0 }.min()
Both will be optional values. 0 will be ignored, but you can include it by modifying one of the operators.
([60, 21, -18, -57, -95, -67, -29, 8, 45, 82] as [Double])
.consecutivePairs
.filter { $0.sign != $1.sign }
public extension Sequence {
/// Each element of the sequence, paired with the element after.
var consecutivePairs: Zip2Sequence<Self, DropFirstSequence<Self>> {
zip(self, dropFirst())
}
}
You just need to check if the last value is greater than zero and the current is less than zero, if true add the range to the results. Just make sure you add the lower value as the lower bound and the upper value as the upper bound:
let values: [Double] = [60, 21, -18, -57, -95, -67, -29, 8, 45, 82]
var last = values.last ?? 0
let ranges = values.dropFirst().compactMap { value -> ClosedRange<Double>? in
defer { last = value }
return last > 0 && value < 0 || last < 0 && value > 0 ? min(last,value)...max(last,value) : nil
}
print(ranges) // [ClosedRange(-18.0...21.0), ClosedRange(-29.0...8.0)]
This query is a small twist on a commonly asked question.
The goal is to filter a 2d array to remove duplicate element pairs whose first-column values are duplicates. For example:
[[1, 1, 1, 2, 2, 3, 3, 3, 3], [0.1, 0.15, 0.2, 0.05, 0.1, 0.2, 0.25, 0.3, 0.35]]
-> [[1, 2, 3],[0.2, 0.1, 0.35]]
Since the second-column values vary, there is obviously some discretion that needs to be applied when filtering: here, the last value of the set of duplicates is chosen.
One of the myriad answers to this related question -- a functional programming solution by Tim MB -- can be adapted to the task:
// Use FP-style filtering to eliminate repeated elements
let rawArray: [[Float]] = [...]
let filteredArray = rawArray
.transpose
.enumerated()
.filter{ (rawArray[0]).lastIndex(of: $0.1[0]) == $0.0 }
.map{ $0.1 }
.transpose
However, this solution is rather slow, which is unfortunate because it's elegant.
A faster solution that keeps the FP spirit is to use dictionary hashing:
// Use array -> dict -> array trick to remove repeated elements
let rawArray: [[Float]] = [...]
let filteredArray = Array( Array(
rawArray
.transpose
.reduce(into: [:], { dict, elements in
dict[elements[0], default:(0,0)] = elements[1]
} )
.map{ ($0.key, $0.value) } )
.sorted{ $0.0 < $1.0 }
.map{ [$0.0, $0.1] }
.transpose) as! Array2D
My questions are:
Is this dictionary trick a good idea? Given that it uses floats as keys?
Why is the FP solution slow? Can it be speeded up?
Are there better alternatives?
Note on terminology: I'll use a to refer to your array, length to refer to its count (a.count), and width to refer to its elements widths (a[0].count).
There are a few things here that are each pretty brutal on your performance.
Transposing
Firstly, each array transposition is O(width * height). Depending on the implementation, it could also be particularly rough on your cache. And you do it twice. Thus, it's an important goal to avoid transposition is possible.
In your case, since you have vectors with only two elements, you can use zip to iterate your two column vectors in tandem. The result a sequence that does so lazily, so no copying happens, and no extra memory or time is used.
Deduplication
The implementation of deduplication that you stumbled on (.filter{ (rawArray[0]).lastIndex(of: $0.1[0]) == $0.0 }) is hot garbage. It's also O(width * height). It's actually worse than approaches that use Array.contains to maintain an array of "already seen" elements. When contains is looking for an element, it can bail-early when it finds a match. lastIndex(of:) always has to go through the entire array, never early-returning because there could always be a later instance of the sought-after element.
Where possible, use an implementation that takes advantage of the Hashability of your elements. Using a Set to track the "already seen" elements allows you to do O(1) contains checks, over array's O(count). I strongly recommend Cœur's implementation.
There's only one catch: that implementation only keeps the first elements, not the last. Luckily enough, that's really easy to fix: just reverse the elements, unique them (keeping the firsts of the reversed elemtents is like keeping the lasts of the original elements), and reverse them back.
My solution:
extension Sequence {
/// Returns an array containing, in order, the first instances of
/// elements of the sequence that compare equally for the keyPath.
func unique<T: Hashable>(for keyPath: KeyPath<Element, T>) -> [Element] {
var unique = Set<T>()
return filter { unique.insert($0[keyPath: keyPath]).inserted }
}
}
let points = zip(array[0], array[1])
let pointsUniquedByXs = points.reversed() // O(1) for collections
.unqiue() // O(count)
.reversed() // O(1) until you need to materalize as a reversed collection
You can accomplish what you want by first filtering the indices of the first array which the element is the first occurrence in reverse order. Then you just need to map the subsequences using them:
let rawArray: [[Float]] = [[1, 1, 1, 2, 2, 3, 3, 3, 3], [0.1, 0.15, 0.2, 0.05, 0.1, 0.2, 0.25, 0.3, 0.3]]
var set: Set<Float> = []
let indices = rawArray
.first?
.indices
.reversed()
.filter { set.insert(rawArray.first![$0]).inserted }
.reversed() ?? []
let result = rawArray.map { elements in indices.map { elements[$0] } }
print(result) // [[1, 2, 3], [0.2, 0.1, 0.3]]
Another option is to create two empty subsequences, iterate the first rawArray subsequence indices reversed and try to insert the float value into a set, if inserted append the corresponding elements to the subsequences, then you just need to recreate the resulting array with those two new sequences reversed:
let rawArray: [[Float]] = [[1, 1, 1, 2, 2, 3, 3, 3, 3], [0.1, 0.15, 0.2, 0.05, 0.1, 0.2, 0.25, 0.3, 0.3]]
var set: Set<Float> = []
var sub1: [Float] = []
var sub2: [Float] = []
rawArray[0].indices.reversed().forEach {
let value = rawArray[0][$0]
if set.insert(value).inserted {
sub1.append(value)
sub2.append(rawArray[1][$0])
}
}
let result: [[Float]] = [sub1.reversed(), sub2.reversed()] // [[1, 2, 3], [0.2, 0.1, 0.3]]
You can make it even faster if the result array is declared as a reversed collection of floating points. It would be O(1) for [ReversedCollection<[Float]>] instead of O(n) for [[Float]] for each subsequence.
Thanks to Alexander, here is solution adapted from Coeur's method in the long related thread.
let rawArray: [[Float]] = [[1, 1, 1, 2, 2, 3, 3, 3, 3],
[0.1, 0.15, 0.2, 0.05, 0.1, 0.2, 0.25, 0.3, 0.35]]
let filteredArray = rawArray
.transpose
.reversed()
.map{ ($0[0],$0[1]) }
.unique(for: \.0)
.map{ [$0.0,$0.1] }
.reversed()
.transpose
All that cruft arises because the data is a two-column float array rather than a 1d array of tuples, and because the last rather than the first duplicate value is required to be selected.
For this to work, Array needs to have the following extensions, the first courtesy of Alexander and Coeur, the second (revision) thanks to Leo Dabus:
extension RangeReplaceableCollection {
/// Returns a collection containing, in order, the first instances of
/// elements of the sequence that compare equally for the keyPath.
func unique<T: Hashable>(for keyPath: KeyPath<Element, T>) -> Self {
var unique = Set<T>()
return filter { unique.insert($0[keyPath: keyPath]).inserted }
}
}
extension RandomAccessCollection where Element: RandomAccessCollection {
/// Peform a transpose operation
var transpose: [[Element.Element]] {
guard !isEmpty,
var index = first?.startIndex,
let endIndex = first?.endIndex
else { return [] }
var result: [[Element.Element]] = []
while index < endIndex {
result.append(map{$0[index]})
first?.formIndex(after: &index) }
return result
}
}
I was trying to make a function that can iterate through any string and return the most common character within that string. My progress is shown below. I was trying to insert the character array into a dictionary where I could then print out the dictionary containing every character and their count. For the people that Think no effort was put in, I merely left out all of the code that I have tried and commented out. Didn't see any use for that so only the essentials were included.
let str = "sunday, monday, happy days"
var charStr = Array(str.characters)
var charDict = Dictionary<Character,Int>()
print("This is the character string array: " , charStr)
You can easily iterate through your characters and increase the number of occurrences of it in your dictionary:
Swift 3
let str = "sunday, monday, happy days"
var charDict: [Character: Int] = [:]
for char in str.characters {
charDict[char] = (charDict[char] ?? 0) + 1
}
print(charDict) // ["d": 3, "u": 1, "a": 4, "h": 1, ",": 2, "n": 2, " ": 3, "m": 1, "o": 1, "y": 4, "s": 2, "p": 2]
You can use max method on your character collection to get the maximum value of your dictionary
if let mostFrequent = charDict.max(by: { $0.value < $1.value }) {
let chars = charDict.filter { $0.value == mostFrequent.value }
.map { $0.key }
.sorted()
print("The most frequent characters are:", chars) // ["a", "y"]
print("Number of occurences:", mostFrequent.value) // 4
}
The most frequent character is: a
Number of occurences: 4
I have an array of array of doubles. How do you convert that to a single string printed on the console?
I need to create a function that takes in an array of arrays of doubles as a parameter and returns a string object. Then loop through each of the inner arrays and concatenate each element of the inner arrays into a String object. But I'm not sure how to go about that.
var array1 = [2.6, 6.7, 7.2, 4.1, 3.1]
var array2 = [1.2, 3.5, 2.8, 4.5, 6.4]
var array3 = [1.2, 1.3, 1.4, 1.5, 1.6]
var nestedArray = [array1, array2, array3]
This is the code I have... but i don't know how to do a for loop that would give me my answer...
func nestedFunction(nestedArray: [[Double]]) -> String {
var stringVar: String = ""
( for loop here... )
return stringVar
}
print(nestedArrayFunction(nestedArray))
The expected output should be a string object, with no brackets
If you want it without the brackets:
let string = nestedArray.flatMap { (array) in
array.flatMap { String($0) }.joinWithSeparator(",")
}.joinWithSeparator(",")
Output:
"2.6,6.7,7.2,4.1,3.1,1.2,3.5,2.8,4.5,6.4,1.2,1.3,1.4,1.5,1.6"
Mind that using , as separator isn't localisation proof, and in French locale would result in a string like "2,6,6,7,7,2" and so on.
From the description in your question it seems as if the argument of nestedFunction(...) should be an array of arrays with double valued elements ([[Double]]) rather than an array with double valued elements ([Double]).
You can make use of .flatten() to your simply nested array nestedArray in nestedFunction(...), and thereafter e.g. reduce to transform the Double valued elements of the flattened array to one concenated String.
var array1 = [2.6, 6.7, 7.2, 4.1, 3.1]
var array2 = [1.2, 3.5, 2.8, 4.5, 6.4]
var array3 = [1.2, 1.3, 1.4, 1.5, 1.6]
var nestedArray = [array1, array2, array3]
func nestedFunction (nestedArray: [[Double]])-> String {
return String(nestedArray
.flatten()
.reduce("") { $0 + ", " + String($1) }
.characters.dropFirst(2))
}
let asString = nestedFunction(nestedArray)
// asString = "2.6, 6.7, 7.2, 4.1, 3.1, 1.2, 3.5, 2.8, 4.5, 6.4, 1.2, 1.3, 1.4, 1.5, 1.6"
As an alternative, if you're set on using a for loop, you can use for ... in on the flattened array, e.g.:
var array1 = [2.6, 6.7, 7.2, 4.1, 3.1]
var array2 = [1.2, 3.5, 2.8, 4.5, 6.4]
var array3 = [1.2, 1.3, 1.4, 1.5, 1.6]
var nestedArray = [array1, array2, array3]
func nestedFunction (nestedArray: [[Double]])-> String {
var stringVar: String = ""
var isFirstElement = true
for elem in nestedArray.flatten() {
stringVar += (isFirstElement ? "" : ", ") + String(elem)
isFirstElement = false
}
return stringVar
}
let asString = nestedFunction(nestedArray)
// asString = "2.6, 6.7, 7.2, 4.1, 3.1, 1.2, 3.5, 2.8, 4.5, 6.4, 1.2, 1.3, 1.4, 1.5, 1.6"
Note that due to limited floating point precision, some double values might end up with a "messy" String representation (e.g. 2.6 might end up as 2.6000000000000001) when using the direct String initializer (String($1) and String(elem) above, in the first and second method, respectively). To redeem this you could set a fixed number of fraction digits for the String representation of your Double values, using the following String initializer:
String(format: "%.3f", myDoubleValue)
/* \
number of fraction digits (3 here) */
E.g., replace String($1) and String(elem) in the methods above by String(format: "%.3f", $1) and String(format: "%.3f", elem), respectively, for some number of fraction digits of your choice. The Double values will be rounded to the number of supplied fraction digits.
Array conforms to CustomStringConvertible, so you can just use its description property:
nestedArray.description
output:
[[2.6000000000000001, 6.7000000000000002, 7.2000000000000002, 4.0999999999999996, 3.1000000000000001], [1.2, 3.5, 2.7999999999999998, 4.5, 6.4000000000000004], [1.2, 1.3, 1.3999999999999999, 1.5, 1.6000000000000001]]
This is what the regular print function uses to print such arrays.
First, you will need to concatenate, or combine, the arrays you have. You can do that simply by creating another array, like this;
let fullArray = array1 + array2 + array3
Next, you can print the full array,
print(fullArray)
Which would give you something like
[2.6000000000000001, 6.7000000000000002, 7.2000000000000002, 4.0999999999999996, 3.1000000000000001, 1.2, 3.5, 2.7999999999999998, 4.5, 6.4000000000000004]
or you can create a string representation of this array using the .joinWithSeparator(String: String) method, like so
let stringRepresentation = fullArray.joinWithSeparator(",")
print(stringRepresentation)
Hope this helps!
Given an array of Swift numeric values, how can I find the minimum and maximum values?
I've so far got a simple (but potentially expensive) way:
var myMax = sort(myArray,>)[0]
And how I was taught to do it at school:
var myMax = 0
for i in 0..myArray.count {
if (myArray[i] > myMax){myMax = myArray[i]}
}
Is there a better way to get the minimum or maximum value from an integer Array in Swift? Ideally something that's one line such as Ruby's .min and .max.
Given:
let numbers = [1, 2, 3, 4, 5]
Swift 3:
numbers.min() // equals 1
numbers.max() // equals 5
Swift 2:
numbers.minElement() // equals 1
numbers.maxElement() // equals 5
To calculate an array's min and max values yourself, you can use reduce. This was a key solution prior to .min() and .max() appearing in Swift.
Use the almighty reduce:
let nums = [1, 6, 3, 9, 4, 6];
let numMax = nums.reduce(Int.min, { max($0, $1) })
Similarly:
let numMin = nums.reduce(Int.max, { min($0, $1) })
reduce takes a first value that is the initial value for an internal accumulator variable, then applies the passed function (here, it's anonymous) to the accumulator and each element of the array successively, and stores the new value in the accumulator. The last accumulator value is then returned.
With Swift 5, Array, like other Sequence Protocol conforming objects (Dictionary, Set, etc), has two methods called max() and max(by:) that return the maximum element in the sequence or nil if the sequence is empty.
#1. Using Array's max() method
If the element type inside your sequence conforms to Comparable protocol (may it be String, Float, Character or one of your custom class or struct), you will be able to use max() that has the following declaration:
#warn_unqualified_access func max() -> Element?
Returns the maximum element in the sequence.
The following Playground codes show to use max():
let intMax = [12, 15, 6].max()
let stringMax = ["bike", "car", "boat"].max()
print(String(describing: intMax)) // prints: Optional(15)
print(String(describing: stringMax)) // prints: Optional("car")
class Route: Comparable, CustomStringConvertible {
let distance: Int
var description: String { return "Route with distance: \(distance)" }
init(distance: Int) {
self.distance = distance
}
static func ==(lhs: Route, rhs: Route) -> Bool {
return lhs.distance == rhs.distance
}
static func <(lhs: Route, rhs: Route) -> Bool {
return lhs.distance < rhs.distance
}
}
let routes = [
Route(distance: 20),
Route(distance: 30),
Route(distance: 10)
]
let maxRoute = routes.max()
print(String(describing: maxRoute)) // prints: Optional(Route with distance: 30)
#2. Using Array's max(by:) method
If the element type inside your sequence does not conform to Comparable protocol, you will have to use max(by:) that has the following declaration:
#warn_unqualified_access func max(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> Element?
Returns the maximum element in the sequence, using the given predicate as the comparison between elements.
The following Playground codes show to use max(by:):
let dictionary = ["Boat" : 15, "Car" : 20, "Bike" : 40]
let keyMaxElement = dictionary.max(by: { (a, b) -> Bool in
return a.key < b.key
})
let valueMaxElement = dictionary.max(by: { (a, b) -> Bool in
return a.value < b.value
})
print(String(describing: keyMaxElement)) // prints: Optional(("Car", 20))
print(String(describing: valueMaxElement)) // prints: Optional(("Bike", 40))
class Route: CustomStringConvertible {
let distance: Int
var description: String { return "Route with distance: \(distance)" }
init(distance: Int) {
self.distance = distance
}
}
let routes = [
Route(distance: 20),
Route(distance: 30),
Route(distance: 10)
]
let maxRoute = routes.max(by: { (a, b) -> Bool in
return a.distance < b.distance
})
print(String(describing: maxRoute)) // prints: Optional(Route with distance: 30)
The other answers are all correct, but don't forget you could also use collection operators, as follows:
var list = [1, 2, 3, 4]
var max: Int = (list as AnyObject).valueForKeyPath("#max.self") as Int
you can also find the average in the same way:
var avg: Double = (list as AnyObject).valueForKeyPath("#avg.self") as Double
This syntax might be less clear than some of the other solutions, but it's interesting to see that -valueForKeyPath: can still be used :)
You can use with reduce:
let randomNumbers = [4, 7, 1, 9, 6, 5, 6, 9]
let maxNumber = randomNumbers.reduce(randomNumbers[0]) { $0 > $1 ? $0 : $1 } //result is 9
Swift 3.0
You can try this code programmatically.
func getSmallAndGreatestNumber() -> Void {
let numbers = [145, 206, 116, 809, 540, 176]
var i = 0
var largest = numbers[0]
var small = numbers[0]
while i < numbers.count{
if (numbers[i] > largest) {
largest = numbers[i]
}
if (numbers[i] < small) {
small = numbers[i]
}
i = i + 1
}
print("Maximum Number ====================\(largest)")// 809
print("Minimum Number ====================\(small)")// 116
}
With Swift 1.2 (and maybe earlier) you now need to use:
let nums = [1, 6, 3, 9, 4, 6];
let numMax = nums.reduce(Int.min, combine: { max($0, $1) })
For working with Double values I used something like this:
let nums = [1.3, 6.2, 3.6, 9.7, 4.9, 6.3];
let numMax = nums.reduce(-Double.infinity, combine: { max($0, $1) })
In Swift 2.0, the minElement and maxElement become methods of SequenceType protocol, you should call them like:
let a = [1, 2, 3]
print(a.maxElement()) //3
print(a.minElement()) //1
Using maxElement as a function like maxElement(a) is unavailable now.
The syntax of Swift is in flux, so I can just confirm this in Xcode version7 beta6.
It may be modified in the future, so I suggest that you'd better check the doc before you use these methods.
Here's a performance test for the solutions posted here. https://github.com/tedgonzalez/MaxElementInCollectionPerformance
This is the fastest for Swift 5
array.max()
var numbers = [1, 2, 7, 5];
var val = sort(numbers){$0 > $1}[0];
Apple's Swift Algorithms introduced 2021 contains Minima and/or Maxima which is likely highly optimized.
Example from the documentation:
let numbers = [7, 1, 6, 2, 8, 3, 9]
if let (smallest, largest) = numbers.minAndMax(by: <) {
// Work with 1 and 9....
}
The total complexity is O(k log k + nk), which will result in a runtime close to O(n) if k is a small amount. If k is a large amount (more than 10% of the collection), we fall back to sorting the entire array. Realistically, this means the worst case is actually O(n log n).
Updated for Swift 3/4:
Use below simple lines of code to find the max from array;
var num = [11, 2, 7, 5, 21]
var result = num.sorted(){
$0 > $1
}
print("max from result: \(result[0])") // 21
Just curious why you think the way it was taught in school is potentially expensive? You're running a for loop which has the time complexity of O(N). That's actually better than most sorting algorithms and equivalent to the performance of higher-order methods like reduce.
So I would think that in terms of performance, a for loop is as good as it gets. I don't think you'll find anything better than O(N) for finding max.
Of course, just use the .max() method provided by apple is the way to go.
If both minimum and maximum values are desired, an efficient approach could be to use a single reduce operation with a tuple:
let values = [11, 2, 7, 5, 21]
let (minimum, maximum) = values.reduce((Int.max, Int.min)) {
(min($0.0, $1), max($0.1, $1))
}
print(minimum, maximum) // 2, 21
let array: [Int] = [2, -22, -1, -5600, 333, -167]
var min = 0
var max = array[0]
for i in array {
// finding the min
if min > i {
min = i
}
// finding the max
if max < i {
max = i
}
}
print("Minimum: \(min)\nMaximum: \(max)")
You can also sort your array and then use array.first or array.last