In Swift, is there a clever way of using the higher order methods on Array to return the 5 first objects?
The obj-c way of doing it was saving an index, and for-loop through the array incrementing index until it was 5 and returning the new array. Is there a way to do this with filter, map or reduce?
13 Answers
By far the neatest way to get the first N elements of a Swift array is using prefix(_ maxLength: Int):
let array = [1, 2, 3, 4, 5, 6, 7]
let slice5 = array.prefix(5) // ArraySlice
let array5 = Array(slice5) // [1, 2, 3, 4, 5]
the one-liner is:
let first5 = Array(array.prefix(5))
This has the benefit of being bounds safe. If the count you pass to prefix is larger than the array count then it just returns the whole array.
NOTE: as pointed out in the comments, Array.prefix actually returns an ArraySlice, not an Array.
If you need to assign the result to an Array type or pass it to a method that's expecting an Array param, you will need to force the result into an Array type: let first5 = Array(array.prefix(5))
8 Comments
dropFirst and dropLast, so might as well have takeFirst and takeLast.first5 to be an array, just write let first5 = Array(someArray.prefix(5))video = video.prefix(5) in Xcode 7.2 results in compile error Cannot assign value of type 'ArraySlice<Video>' to type '[Video]'video = Array(video.prefix(5))prefix is possibly Swift 2.x only (I don't remember if it was in 1.x), but it certainly exists in any OS that supports Swift 2.x, which is iOS 7 and above. Swift feature availability is determined by Swift releases, not iOS versions.You can do it really easy without filter, map, reduce, or prefix by just returning a range of your array via a subscript:
var wholeArray = [1, 2, 3, 4, 5, 6]
var n = 5
var firstFiveSlice = wholeArray[0..<n] // 1,2,3,4,5
let firstFiveArray = Array(firstFiveSlice)
5 Comments
n items from a Swift array you can do wholeArray.prefix(n) which has the added benefit of being bounds safe. If n is larger than the array size prefix returns the whole array.wholeArray doesn't have more elements than nprefix.With Swift 5, according to your needs, you may choose one of the 6 following Playground codes in order to solve your problem.
#1. Using subscript(_:) subscript
let array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"]
let arraySlice = array[..<5]
//let arraySlice = array[0..<5] // also works
//let arraySlice = array[0...4] // also works
//let arraySlice = array[...4] // also works
let newArray = Array(arraySlice)
print(newArray) // prints: ["A", "B", "C", "D", "E"]
#2. Using prefix(_:) method
Complexity: O(1) if the collection conforms to RandomAccessCollection; otherwise, O(k), where k is the number of elements to select from the beginning of the collection.
let array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"]
let arraySlice = array.prefix(5)
let newArray = Array(arraySlice)
print(newArray) // prints: ["A", "B", "C", "D", "E"]
Apple states for prefix(_:):
If the maximum length exceeds the number of elements in the collection, the result contains all the elements in the collection.
#3. Using prefix(upTo:) method
Complexity: O(1)
let array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"]
let arraySlice = array.prefix(upTo: 5)
let newArray = Array(arraySlice)
print(newArray) // prints: ["A", "B", "C", "D", "E"]
Apple states for prefix(upTo:):
Using the
prefix(upTo:)method is equivalent to using a partial half-open range as the collection's subscript. The subscript notation is preferred overprefix(upTo:).
#4. Using prefix(through:) method
let array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"]
let arraySlice = array.prefix(through: 4)
let newArray = Array(arraySlice)
print(newArray) // prints: ["A", "B", "C", "D", "E"]
#5. Using removeSubrange(_:) method
Complexity: O(n), where n is the length of the collection.
var array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"]
array.removeSubrange(5...)
print(array) // prints: ["A", "B", "C", "D", "E"]
#6. Using dropLast(_:) method
Complexity: O(1) if the collection conforms to RandomAccessCollection; otherwise, O(k), where k is the number of elements to drop.
let array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"]
let distance = array.distance(from: 5, to: array.endIndex)
let arraySlice = array.dropLast(distance)
let newArray = Array(arraySlice)
print(newArray) // prints: ["A", "B", "C", "D", "E"]
1 Comment
SWIFT 4
A different solution:
An easy inline solution that wont crash if your array is too short
[0,1,2,3,4,5].enumerated().compactMap{ $0.offset < 3 ? $0.element : nil }
But works fine with this.
[0,1,2,3,4,5].enumerated().compactMap{ $0.offset < 1000 ? $0.element : nil }
Usually this would crash if you did this:
[0,1,2,3,4,5].prefix(upTo: 1000) // THIS CRASHES
[0,1,2,3,4,5].prefix(1000) // THIS DOESNT
2 Comments
[0,1,2,3,4,5].enumerated().flatMap{ $0.offset < 1000 ? $0.element : nil }For getting the first 5 elements of an array, all you need to do is slice the array in question. In Swift, you do it like this: array[0..<5].
To make picking the N first elements of an array a bit more functional and generalizable, you could create an extension method for doing it. For instance:
extension Array {
func takeElements(var elementCount: Int) -> Array {
if (elementCount > count) {
elementCount = count
}
return Array(self[0..<elementCount])
}
}
1 Comment
For an array of objects you can create an extension from Sequence.
extension Sequence {
func limit(_ max: Int) -> [Element] {
return self.enumerated()
.filter { $0.offset < max }
.map { $0.element }
}
}
Usage:
struct Apple {}
let apples: [Apple] = [Apple(), Apple(), Apple()]
let limitTwoApples = apples.limit(2)
// limitTwoApples: [Apple(), Apple()]
1 Comment
Take this extension. It's fixing original horrible naming:
public extension Array {
func first(_ count: Int) -> ArraySlice<Element> {
return self.prefix(count)
}
func last(_ count: Int) -> ArraySlice<Element> {
return self.suffix(count)
}
func take(_ range: ClosedRange<Int>) -> ArraySlice<Element> {
guard self.count > 0 else { return [] }
let lower = Swift.max(0, range.lowerBound)
let upper = Swift.min(self.count - 1, range.upperBound)
guard lower <= upper else { return [] }
return self[lower...upper]
}
}
usage:
someArr.first(5)
someArr.last(5)
someArr.take(0...5)
Why "take()" function needed?
Because of you will get Fatal error: Array index is out of range in lot of use-cases.
But with "take()" you will not.
Comments
I slightly changed Markus' answer to update it for the latest Swift version, as var inside your method declaration is no longer supported:
extension Array {
func takeElements(elementCount: Int) -> Array {
if (elementCount > count) {
return Array(self[0..<count])
}
return Array(self[0..<elementCount])
}
}
1 Comment
Plain & Simple
extension Array {
func first(elementCount: Int) -> Array {
let min = Swift.min(elementCount, count)
return Array(self[0..<min])
}
}
1 Comment
Swift 4 with saving array types
extension Array {
func take(_ elementsCount: Int) -> [Element] {
let min = Swift.min(elementsCount, count)
return Array(self[0..<min])
}
}
1 Comment
The Prefix function is definitely the most efficient way of solving this problem, but you can also use for-in loops like the following:
let array = [1,2,3,4,5,6,7,8,9]
let maxNum = 5
var iterationNumber = 0
var firstNumbers = [Int()]
if array.count > maxNum{
for i in array{
iterationNumber += 1
if iterationNumber <= maxNum{
firstNumbers.append(i)
}
}
firstNumbers.remove(at: 0)
print(firstNumbers)
} else {
print("There were not \(maxNum) items in the array.")
}
This solution takes up many lines of code but checks to see if there are enough items in the array to carry out the program, then continues and solves the problem.
This solution uses many basic functions including array.count, which returns the amount of items in the array, not the position of last item in the array. It also uses array.append, which adds things onto the end of the array. Lastly, it uses array.remove, which removes the array's item that has a specified position.
I have tested it it and it works for at least swift 5.
nelements of anArray.