I'm learning about High Order functions in Swift (like .map .filter .reduce...) and generics 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 my for loop with high order function to get the same result ?
Im looking to do something like this (or better) :
max = array.map { $0 > max ? $0 : max }
Reduce!
return array.reduce(nil)
{
(max: T?, current: T) -> T? in
guard let max = max else { return current }
return max > current ? max : current
}
That will return an optional but that is probably sensible given you might pass in an empty array.
Of course there is also this
https://developer.apple.com/documentation/swift/array/1688806-max
The implication of tyour question is that this is a learning exercise. So here is a generalisation of the solution that makes use of higher order functions. Note that the Swft Strandard Library already contains a function 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 the functionality of max is now defined like this:
array.pickOne { $0 > $1 ? $0 : $1 }
and min would be
array.pickOne { $0 < $1 ? $0 : $1 }
First note that your approach for the "initial value" and the forced
cast
var max = 0 as! T
has two problems:
It will crash for arrays not containing integers, e.g. max(["a", "b"]).
Even for integer arrays, it is wrong if all array elements are
negative, e.g. max([-2, -3]) should be -2 and not zero.
So you better choose the first array element as initial value instead
of the "forced zero".
That leads to the next question: What if the array
is empty? There are two valid approaches: You can require that the
function is called with a non-empty array (and document that
precondition):
/// 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 the 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 implemented with reduce().
The first one would 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 the second one
/// 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 using the flatMap() method
of 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 the existing
func max<T : Comparable>(_ x: T, _ y: T) -> T
function instead of a literal closure in all of the above examples, e.g.
/// 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) }
}
For Ints you would want to use reduce for this like so:
// 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 a proper handling of all Comparable!
Related
I am asking a user for four ranges of floating point numbers. I want to check that there is no overlap between them.
If the ranges were integer ranges it seems that I could either create sets or use Swift Range (or NSRange) and check for intersections.
Is there a way to figure this out if the ranges where then upper and lower bounds are floating point values?
Would I just have to check that each lower and upper bound of each range is not between the lower/upper bound of each of the other ranges? Is there a better way?
Thanks
You can use the Range type to represent a range of floating point values. You can then use the contains method to check if a value is in a range. You can also use the overlaps method to check if two ranges overlap. Here is an example:
let range1 = 1.0..<2.0
let range2 = 1.5..<3.0
let range3 = 2.0..<4.0
range1.contains(1.5) // true
range1.overlaps(range2) // true
range1.overlaps(range3) // false
You can also use the ~= operator to check if a value is in a range. This is the same operator that is used in switch statements.
let value = 1.5
switch value {
case range1:
if range1.contains(value) {
print("value is in range1")
}
case range2:
if range2.contains(value) {
print("value is in range2")
}
case range3:
if range3.contains(value) {
print("value is in range3")
}
default:
print("value is not in any range")
}
You can use the sorted method to sort an array of ranges. Here is an example:
let ranges = [range1, range2, range3]
let sortedRanges = ranges.sorted { $0.lowerBound < $1.lowerBound }
You can then iterate over the sorted ranges and check if the upper bound of the previous range is greater than the lower bound of the current range.
for (index, range) in sortedRanges.enumerated() {
if index > 0 {
let previousRange = sortedRanges[index - 1]
if previousRange.upperBound > range.lowerBound {
print("ranges overlap")
}
}
}
You can also use the contains/overlaps method to check if a range contains another range.
for (index, range) in sortedRanges.enumerated() {
if index > 0 {
let previousRange = sortedRanges[index - 1]
if range.contains(previousRange) {
print("ranges overlap")
}
if range.overlaps(previousRange) {
print("ranges overlap")
}
}
}
There is no way to express it as a property yet; it has to be a function.
import Algorithms
extension Sequence {
func containsAnOverlap<Bound>() -> Bool
where Element == ClosedRange<Bound> {
sorted(by: \.lowerBound).adjacentPairs().contains { $0.overlaps($1) }
}
}
public extension Sequence {
/// Sorted by a common `Comparable` value.
func sorted(
by comparable: (Element) throws -> some Comparable
) rethrows -> [Element] {
try sorted(by: comparable, <)
}
/// Sorted by a common `Comparable` value, and sorting closure.
func sorted<Comparable: Swift.Comparable>(
by comparable: (Element) throws -> Comparable,
_ areInIncreasingOrder: (Comparable, Comparable) throws -> Bool
) rethrows -> [Element] {
try sorted {
try areInIncreasingOrder(comparable($0), comparable($1))
}
}
}
[2...3.0, 0...1.0].containsAnOverlap() // false
[1...3.0, 0...2.0].containsAnOverlap() // true
I get the above message in a project, I was trying to test the code and needed the ability to debug.
//
// main.swift
// Chapter7Example2
//
// Created by Mark Edward King on 20/04/2021.
//
import Foundation
func removingOnce(_ item: Int, from array: inout [Int]) -> [Int] {
for i in 0..<array.count {
if array[i] == item {
// Swap the first two elements
array.remove(at: i)
return array
}
}
return array
}
var arrayVal : [Int] = [1, 2, 3, 4, 5]
arrayVal = removingOnce( 2, from: &arrayVal)
for i in arrayVal {
print(i)
}
func removing(_ item: Int, from array: inout [Int]) -> [Int] {
var notFound = true
var arrayRes : [Int] = []
for i in array {
if array[i] != item {
notFound = false
}
}
if notFound == false {
return array
} else if array == [] {
return array
} else {
for i in array {
if array[i] == item {
// Remove the first element
array.remove(at: i)
return arrayRes = removing(item, from: &Array(array[1..<array.count]))
}
}
return array
}
}
func reverse( from array: inout [Int]) -> [Int] {
var arrayRes : [Int] = []
if array == [] {
return array
}
else if array.count == 1 {
return array
}
else {
// Swap first two members
array.swapAt(0, 1)
// Break down the array into smaller parts
arrayRes = reverse( &Array(array[2..<array.count]))
// Append one of the arrays which has been reversed and append the solved part
return array[0...1] += arrayRes
}
}
var arrayVal2 : [Int] = [1, 2, 2, 2, 5]
arrayVal2 = removing( 2, from: &arrayVal2)
for i in arrayVal2 {
print(i)
}
/*func middle(_ array: [Int]) -> Int? {
if array.count % 2 == 0 {
return array[array.count / 2 - 1]
}
else {
return array[array.count / 2]
}
}*/
var arrayVal3 : [Int] = [1, 2, 3, 4, 5]
I just need help with the removing function. There are actually two errors but. The other error is cannot convert return type of () to [Int], not sure why this is related so help on this problem would also be appreciated.
There are errors at almost every lines of your removing function, I'll try to address each one assuming that your function is supposed to remove every occurence of item.
func removing(_ item: Int, from array: inout [Int]) -> [Int] {
As said by #Shadowrun, you must choose between returning the modified array as the return type or updating it via the inout parameter, not both.
var array = array
Here you make a local copy of array with a shadowing name (i.e. you use the same name), which defeats the purpose of using an inout parameter as you never update it.
var arrayRes : [Int]
Using an initialized variable is very bad practice in Swift, at least you should initialize and empty array.
for i in array {
if array[i] != item {
notFound = false
}
}
You tried to iterate over the array elements instead of the array indices. The fast that the compiler did not complained is by pure coincidence as array elements are also of Int type, the same as Swift array indices.
Another issue is that you don't really need indices but only elements, here is the correct way:
for element in array {
if element != item {
notFound = false
}
}
if notFound == false {
...
} else if array == [] {
...
} else {
for element in array {
if element == item { ... }
}
}
What is inside the if condition of the else branch will never be executed because you previously checked that notFound == false in the first if condition. This means that notFound is necessarily true in the else branch, meaning all elements are different that item, so element == item always returns false.
array.remove(at: i)
Doing an operation that mutates the array you are currently iterating is almost always guaranteed to be an error and will certainly cause you troubles and runtime errors at some point (do it only if you know that it is safe).
return arrayRes = removing(item, from: &array[1...array.count])
This is wrong in many ways:
you are returning the value of an assignment, i.e a code line with a = sign. This does not make sense as you probably want to return the value of removing(...) rather than the value of the assignment. And an assignment does not have any value (materialized by the Void type).
This call (minus the previous error) is recursive, I don't now if this is intended but I doubt you understand how this works.
The removing(...) func accepts an array to [Int] as argument, here you are trying to pass an array Slice array[1...array.count]. Also remove the & sign if not using the inout parameter.
I hope that you know that Swift array indices start at 0, not 1 and that the 1 is intended in your code.
All that said, the origin of your error is here:
return arrayRes = removing(item, from: &Array(array[1...array.count]))
Array(array[1...array.count]) is an immutable object because is is instantiated inline, at the call site. However, it should be mutable because removing accepts an inout parameter, which requires the parameter to be mutable.
The second error is at the same line, you are trying to return the value of an assignment (statement with an = sign) instead of the return value of the function. An assignment has a return type of Void (meaning that it does not return something useful) which is incompatible with your function return type: [Int].
I don't know if this is homework or some kind of programming training, but if this is the case you should start with the basis, the Official Swift Book by Apple is a good starting point. If not the case you can achieve the same thing with:
var array = [1, 2, 3]
func removing(_ item: Int, from array: inout [Int]) {
array.removeAll { $0 == item }
}
removing(2, from: &array)
print(array)
func reversed(_ array: inout ([Int]) -> [Int]) {
Is a function that takes a single argument: an inout function of type [Int] -> [Int] - not what you want
You should either make it a function that mutates a [Int] via an inout [Int] parameter: like func reversed(_ array: inout [Int]) or else make it a function from [Int] -> [Int] like func reversed(_ array: [Int]) -> [Int]
Functions of the form A -> A are equivalent to functions of the form inout A -> Void, and you should pick one style or the other.
How can I convert the function below to to swift 3? Currently getting a Binary operator '..<' cannot be applied to operands of type 'Int' and 'Self.IndexDistance' error.
extension MutableCollection 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 { //error takes place here
let j = Int(arc4random_uniform(UInt32(count - i))) + i
guard i != j else { continue }
swap(&self[i], &self[j])
}
}
}
reference: https://stackoverflow.com/a/24029847/5222077
count returns an IndexDistance which is the type describing
the distance between two collection indices. IndexDistance is
required to be a SignedInteger, but need not be an Int and can
be different from Index. Therefore it is not possible to create
the range 0..<count - 1.
A solution is to use startIndex and endIndex instead of 0 and count:
extension MutableCollection where Index == Int {
/// Shuffle the elements of `self` in-place.
mutating func shuffle() {
// empty and single-element collections don't shuffle
if count < 2 { return }
for i in startIndex ..< endIndex - 1 {
let j = Int(arc4random_uniform(UInt32(endIndex - i))) + i
if i != j {
swap(&self[i], &self[j])
}
}
}
}
Another advantage is that this also works correctly with array slices
(where the index of the first element is not necessarily zero).
Note that according to the new "Swift API Design Guidelines",
shuffle() is the "proper" name for a mutating shuffle method,
and shuffled() for the non-mutating counterpart which returns an array:
extension Collection {
/// Return a copy of `self` with its elements shuffled
func shuffled() -> [Iterator.Element] {
var list = Array(self)
list.shuffle()
return list
}
}
Update: A (even more general) Swift 3 version has been added to
How do I shuffle an array in Swift? in the meantime.
For Swift 4 (Xcode 9) one has to replace the call to the swap()
function by a call to the swapAt() method of the collection.
Also the restriction on the Index type is no longer needed:
extension MutableCollection {
/// Shuffle the elements of `self` in-place.
mutating func shuffle() {
for i in indices.dropLast() {
let diff = distance(from: i, to: endIndex)
let j = index(i, offsetBy: numericCast(arc4random_uniform(numericCast(diff))))
swapAt(i, j)
}
}
}
See SE-0173 Add MutableCollection.swapAt(_:_:) for more information about swapAt.
As of Swift 4.2 (Xcode 10, currently in beta), with the implementation of
SE-0202 Random Unification,
shuffle() and shuffled() are part of the Swift standard library.
There is a fisher-yates shuffle in Gamekit:
import GameKit
let unshuffledArray = [1,2,3,4]
let shuffledArray = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: unshuffledArray)
print(shuffledArray)
You can also pass in and store a random seed, so you get the same sequence of pseudorandom shuffle values every time you supply the same seed in case you need to recreate a simulation.
import GameKit
let unshuffledArray = [1,2,3,4]
let randomSource = GKLinearCongruentialRandomSource(seed: 1)
let shuffledArray = randomSource.arrayByShufflingObjects(in: unshuffledArray)
//Always [1,4,2,3]
print(shuffledArray)
I would suggest simply shuffling arrays instead of trying to extend this to collections in general:
extension Array {
mutating func shuffle () {
for i in (0..<self.count).reversed() {
let ix1 = i
let ix2 = Int(arc4random_uniform(UInt32(i+1)))
(self[ix1], self[ix2]) = (self[ix2], self[ix1])
}
}
}
You can use the NSArray Extension from GameplayKit framework for this:
import GameplayKit
extension Collection {
func shuffled() -> [Iterator.Element] {
let shuffledArray = (self as? NSArray)?.shuffled()
let outputArray = shuffledArray as? [Iterator.Element]
return outputArray ?? []
}
mutating func shuffle() {
if let selfShuffled = self.shuffled() as? Self {
self = selfShuffled
}
}
}
// Usage example:
var numbers = [1,2,3,4,5]
numbers.shuffle()
print(numbers) // output example: [2, 3, 5, 4, 1]
print([10, "hi", 9.0].shuffled()) // output example: [hi, 10, 9]
I am looking for a way to stop a higher-level function after evaluating part of its input sequence.
Consider a situation when you look for the first index in a sequence that satisfies a certain condition. For example, let's say we are looking for the first position in an array a of Ints where the sum of two consecutive values is above 100.
You can do it with a loop, like this:
func firstAbove100(a:[Int]) -> Int? {
if a.count < 2 {
return nil
}
for i in 0..<a.count-1 {
if a[i]+a[i+1] > 100 {
return i
}
}
return nil
}
The looping stops as soon as the position of interest is discovered.
We can rewrite this code using reduce as follows:
func firstAbove100(a:[Int]) -> Int? {
if a.count < 2 {
return nil
}
return (0..<a.count-1).reduce(nil) { prev, i in
prev ?? (a[i]+a[i+1] > 100 ? i : nil)
}
}
However, the disadvantage of this approach is that reduce goes all the way up to a.count-2 even if it finds a match at the very first index. The result is going to be the same, but it would be nice to cut the unnecessary work.
Is there a way to make reduce stop trying further matches, or perhaps a different function that lets you stop after finding the first match?
As already said, reduce is specifically designed in order to evaluate an entire sequence and therefore not designed to short-circuit. Using it in this way to find an the index of an element that meets a given predicate is best done with indexOf as #Casey says.
Also as of Swift 3, there is now a first(where:) function on Sequence that allows you to find the first element that satisfies a given predicate. This could be an even more suitable alternative than indexOf, as it returns the element instead of the index (although in your particular example these are the same).
You could write your example like this:
func firstAbove100(_ a:[Int]) -> Int? {
guard a.count > 1 else {return nil}
return (0..<a.count-1).first { i in
a[i]+a[i+1] > 100
}
}
However if you want a more general high level function that will iterate through a sequence and break out if it finds a non-nil result of a given predicate – you could always write your own find function:
extension SequenceType {
func find<T>(#noescape predicate: (Self.Generator.Element) throws -> T?) rethrows -> T? {
for element in self {
if let c = try predicate(element) {return c}
}
return nil
}
}
You could now write your firstAbove100 function like this:
func firstAbove100(a:[Int]) -> Int? {
if a.count < 2 {
return nil
}
return (0..<a.count-1).find { i in
a[i]+a[i+1] > 100 ? i : nil
}
}
and it will now short-circuit when it finds a pair of elements that add to above 100.
Or let's say instead of returning the index of the first pair of elements in your array that add to greater than 100, you now want to return the sum of the elements. You could now write it like this:
func sumOfFirstAbove100(a:[Int]) -> Int? {
guard a.count > 1 else {return nil}
return (0..<a.count-1).find { i in
let sum = a[i]+a[i+1]
return sum > 100 ? sum : nil
}
}
let a = [10, 20, 30, 40, 50, 60, 70, 80, 90]
print(sumOfFirstAbove100(a)) // prints: Optional(110)
The find function will iterate through the array, applying the predicate to each element (in this case the indices of your array). If the predicate returns nil, then it will carry on iterating. If the predicate returns non-nil, then it will return that result and stop iterating.
indexOf will stop after it finds the first match so you might rewrite firstAbove100 to something like this:
func firstAbove100(a:[Int]) -> Int? {
return a.count > 1 ? (a.startIndex..<a.endIndex-1).indexOf({ a[$0] + a[$0 + 1] > 100 }) : nil
}
Another question asked, essentially, how to implement a take function which would return the first n elements of a sequence. My answer was:
struct TakeFromSequenceSequence<S:SequenceType> : SequenceType {
var limit : Int
var sequence : S
func generate() -> AnyGenerator<S.Generator.Element> {
var generator = sequence.generate()
var limit = self.limit
return anyGenerator {
guard limit > 0 else {
return nil
}
limit = limit - 1
return generator.next()
}
}
}
extension SequenceType {
func take(count:Int) -> TakeFromSequenceSequence<Self> {
return TakeFromSequenceSequence(limit: count, sequence: self)
}
}
but it seems like I ought to be able to use AnySequence and anyGenerator to do it all inline in my take function:
extension SequenceType {
func take(count:Int) -> AnySequence<Self.Generator.Element> {
// cannot invoke initializer for type 'AnySequence<_>' with an argument list of type '(() -> _)'
return AnySequence({
var generator = self.generate()
var limit = count
// cannot invoke 'anyGenerator' with an argument list of type '(() -> _)'
return anyGenerator({
guard limit > 0 else {
return nil
}
limit = limit - 1
return generator.next()
})
})
}
}
Unfortunately, this yields multiple typing errors, mostly (I think) because type inference is failing.
Anybody have any suggestions on how to get this (using AnySequence and anyGenerator inline) to work?
(The answer is now based on Swift 2.2/Xcode 7.3. A solution for Swift 2.1 can be found in the edit history.)
The type of the closure passed to the AnySequence init method
must be specified explicitly:
extension SequenceType {
func take(count:Int) -> AnySequence<Generator.Element> {
return AnySequence { () -> AnyGenerator<Generator.Element> in
var generator = self.generate()
var limit = count
return AnyGenerator {
guard limit > 0 else {
return nil
}
limit = limit - 1
return generator.next()
}
}
}
}
Note that the (redundant) Self. in Self.Generator.Element is omitted, otherwise it does not compile.
Example:
let sequence = [1,2,3,4,5].take(2)
print(Array(sequence)) // [1, 2]
print(Array(sequence)) // [1, 2]
Alternatively, the method can be defined as
extension SequenceType {
func take(count:Int) -> AnySequence<Generator.Element> {
var generator = self.generate()
var limit = count
return AnySequence {
return AnyGenerator {
guard limit > 0 else {
return nil
}
limit = limit - 1
return generator.next()
}
}
}
}
Now the closure passed to the AnySequence init method is a "single-expression closure" and the type is inferred by the compiler.
But – as David Berry noted – the created sequence then behaves differently, the generate() method cannot be called repeatedly
with identical results:
let sequenceX = [1,2,3,4,5].take(2)
print(Array(sequenceX)) // [1, 2]
print(Array(sequenceX)) // []
This is permitted behavior, as stated in the SequenceType protocol reference:
... It is not correct to assume that a sequence will either be
"consumable" and will resume iteration, or that a sequence is a
collection and will restart iteration from the first element. A
conforming sequence that is not a collection is allowed to produce an
arbitrary sequence of elements from the second generator.
So one can choose among these implementations, dependent on the desired behavior.