Assigning high-order functions
I am learning about high order functions in Swift (like .map.filter.reduce ...) and generic types.
Here is my function:
func max<T: Comparable>(_ array: [T]) -> T {
var max = 0 as! T
for value in array {
if value > max { max = value }
}
return max
}
How can I replace the for loop with a higher order function to get the same result?
I would like to do something like this (or better):
max = array.map { $0 > max ? $0 : max }
source to share
First of all, note that your approach to "seed" and forced to cast
var max = 0 as! T
has two problems:
- It will split into non-integer arrays for example.
max(["a", "b"]
). - Even for whole arrays it is not true if all elements of the array are negative, eg.
max([-2, -3])
must be-2
and not equal to zero.
So, it is better to choose the first element of the array as the initial "forced zero" value.
This leads to the next question: what if the array is empty? There are two valid approaches, you can require the function to be called with a non-empty array (and a document, which is the premise):
/// Compute the maximal element in an array.
///
/// - Returns: The maximal element.
///
/// - Note: The array must not be empty.
func max<T: Comparable>(_ array: [T]) -> T {
precondition(!array.isEmpty, "`max` called with empty array")
var max = array[0]
for value in array {
if value > max { max = value }
}
return max
}
Or (as also suggested in other answers) make the return value optional:
/// Compute the maximal element in an array.
///
/// - Returns: `nil` if the array is empty, and the maximal element otherwise.
func max<T: Comparable>(_ array: [T]) -> T? {
guard var max = array.first else { return nil }
for value in array {
if value > max { max = value }
}
return max
}
Both approaches can be accomplished with reduce()
. The first will be
/// Compute the maximal element in an array.
///
/// - Returns: The maximal element.
///
/// - Note: The array must not be empty.
func max<T: Comparable>(_ array: [T]) -> T {
precondition(!array.isEmpty, "`max` called with empty array")
return array.reduce(array[0]) { $0 > $1 ? $0 : $1 }
}
and second
/// Compute the maximal element in an array.
///
/// - Returns: `nil` if the array is empty, and the maximal element otherwise.
func max<T: Comparable>(_ array: [T]) -> T? {
guard let first = array.first else { return nil }
return array.reduce(first) { $0 > $1 ? $0 : $1 }
}
This can be further shortened with the method flatMap()
Optional
:
/// Compute the maximal element in an array.
///
/// - Returns: `nil` if the array is empty, and the maximal element otherwise.
func max<T: Comparable>(_ array: [T]) -> T? {
return array.first.flatMap { array.reduce($0) { $0 > $1 ? $0 : $1 } }
}
Finally, you can use existing
func max<T : Comparable>(_ x: T, _ y: T) -> T
instead of literal closure in all of the above examples, eg.
/// Compute the maximal element in an array.
///
/// - Returns: `nil` if the array is empty, and the maximal element otherwise.
func max<T: Comparable>(_ array: [T]) -> T? {
return array.first.flatMap { array.reduce($0, max) }
}
source to share
Decrease!
return array.reduce(nil)
{
(max: T?, current: T) -> T? in
guard let max = max else { return current }
return max > current ? max : current
}
This will return optional, but it's probably sane if you step through an empty array.
Of course there is this one
https://developer.apple.com/documentation/swift/array/1688806-max
This is an instructional exercise. So, here is a generalization of the solution that uses higher order functions. Note that there is already a function in the Swft Strandard library that does this.
extension Array
{
func pickOne(choose: (Element, Element) -> Element) -> Element?
{
return self.reduce(nil)
{
(bestSoFar: Element?, current: Element) -> Element? in
guard let bestSoFar = bestSoFar else { return current }
return choose(bestSoFar, current)
}
}
}
So now the max functionality is defined like this:
array.pickOne { $0 > $1 ? $0 : $1 }
and min will be
array.pickOne { $0 < $1 ? $0 : $1 }
source to share
For Ints, you would like to use reduce
it like this:
// Reduce with initial value the first value of the array if available,
// or 0 otherwise
let max = array.reduce(array.first ?? 0) { (max, newValue) -> T in
return newValue > max ? newValue : max
}
UPDATE
You want JeremyP's answer for handling the whole Comparable correctly!
source to share