54

Is there a cleaner way to get the last two items of an array in Swift? In general, I try to avoid this approach since it's so easy to be off-by-one with the indexes. (Using Swift 1.2 for this example.)

// Swift -- slices are kind of a hassle?
let oneArray = ["uno"]
let twoArray = ["uno", "dos"]
let threeArray = ["uno", "dos", "tres"]

func getLastTwo(array: [String]) -> [String] {
    if array.count <= 1 {
        return array
    } else {
        let slice: ArraySlice<String> = array[array.endIndex-2..<array.endIndex]
        var lastTwo: Array<String> = Array(slice)
        return lastTwo
    }
}

getLastTwo(oneArray)   // ["uno"]
getLastTwo(twoArray)   // ["uno", "dos"]
getLastTwo(threeArray) // ["dos", "tres"]

I was hoping for something closer to Python's convenience.

## Python -- very convenient slices
myList = ["uno", "dos", "tres"]
print myList[-2:] # ["dos", "tres"]
zekel
  • 9,227
  • 10
  • 65
  • 96
  • 2
    There's already a built-in function that gives exactly the results you specify - `suffix()`. I've added an answer that shows it in action... – matt Jun 24 '15 at 13:27
  • 1
    This QA and the excellent answer might be useful for anyone googling here: http://stackoverflow.com/questions/40312252 – Fattie Oct 30 '16 at 20:32

9 Answers9

97

With Swift 5, according to your needs, you may choose one of the following patterns in order to get a new array from the last two elements of an array.


#1. Using Array's suffix(_:)

With Swift, objects that conform to Collection protocol have a suffix(_:) method. Array's suffix(_:) has the following declaration:

func suffix(_ maxLength: Int) -> ArraySlice<Element>

Returns a subsequence, up to the given maximum length, containing the final elements of the collection.

Usage:

let array = [1, 2, 3, 4]
let arraySlice = array.suffix(2)
let newArray = Array(arraySlice)
print(newArray) // prints: [3, 4]

#2. Using Array's subscript(_:)

As an alternative to suffix(_:) method, you may use Array's subscript(_:) subscript:

let array = [1, 2, 3, 4]
let range = array.index(array.endIndex, offsetBy: -2) ..< array.endIndex
//let range = array.index(array.endIndex, offsetBy: -2)... // also works
let arraySlice = array[range]
let newArray = Array(arraySlice)
print(newArray) // prints: [3, 4]
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
53

myList[-2:]

Yes, I have an enhancement request filed asking for negative index notation, and I suggest you file one too.

However, you shouldn't make this harder on yourself than you have to. The built-in global suffix function does exactly what you're after:

let oneArray = ["uno"]
let twoArray = ["uno", "dos"]
let threeArray = ["uno", "dos", "tres"]

let arr1 = oneArray.suffix(2) // ["uno"]
let arr2 = twoArray.suffix(2) // ["uno", "dos"]
let arr3 = threeArray.suffix(2) // ["dos", "tres"]

The result is a slice, but you can coerce it to an Array if you need to.

Huynh Inc
  • 2,010
  • 1
  • 25
  • 42
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    How could we all miss this for so long? – Martin R Jun 24 '15 at 18:18
  • 2
    @MartinR We forgot to read my book. Including me. http://www.apeth.com/swiftBook/ch04.html#_basic_array_properties_and_methods But I don't take back any of my compliments to the other answers, especially since they suggest how `suffix` might be implemented. This was an astonishingly educational thread. – matt Jun 24 '15 at 18:25
  • 27
    In Swift 2.0 `suffix(MyCoolArray, 3)` becomes `MyCoolArray.suffix(3)` Thought I'd share so no one else pulls as much hair out as I did – Samie Bencherif Feb 02 '16 at 03:17
  • what about **dropFirst:(length-2)** – Fattie Oct 30 '16 at 20:33
  • @Joe Blow It was June 2015, dude. – matt Oct 30 '16 at 20:35
  • lol right on. after asking this question .. [is there a fromAfter](http://stackoverflow.com/questions/40312252) ... it occurred to me to wonder [this question](http://stackoverflow.com/questions/40333438) – Fattie Oct 30 '16 at 20:45
  • Just brilliant! – M. Porooshani Jul 12 '17 at 06:04
13

in swift 5 you can use suffix for get objects from the last and use prefix for get objects from the first, here is an example:

let exampleArray = ["first text", "second text", "third text"]

let arr1 = exampleArray.suffix(2) // ["second text", "third text"]
let arr2 = exampleArray.prefix(2) // ["first text", "second text"]

The result is a slice, but you can coerce it to an Array if you need to.

mohsen
  • 4,698
  • 1
  • 33
  • 54
7

In Swift 2, you can extend CollectionType. Here's an example (borrowing from Rob Napier's answer):

extension CollectionType {
    func last(count:Int) -> [Self.Generator.Element] {
        let selfCount = self.count as! Int
        if selfCount <= count - 1 {
            return Array(self)
        } else {
            return Array(self.reverse()[0...count - 1].reverse())
        }
    }
}

You can use it on any CollectionType. Here's Array:

let array = ["uno", "dos", "tres"]
print(array.last(2)) // [dos, tres]

Here's CharacterView:

let string = "looking"
print(string.characters.last(4)) // [k, i, n, g]

(Note that my example returns an Array in all cases, not the original collection type.)

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
  • 2
    "my example returns an Array in all cases" That seems perfectly reasonable, though; `map` behaves the same way, IIRC. You can always cast back to whatever-it-is if desired. – matt Jun 23 '15 at 18:06
3

More generic answer ...

let a1 = [1,2,3,4,5]
let a2 = ["1","2","3","4","5"]

func getLast<T>(array: [T], count: Int) -> [T] {
  if count >= array.count {
    return array
  }
  let first = array.count - count
  return Array(array[first..<first+count])
}

getLast(a1, count: 2) // [4, 5]
getLast(a2, count: 3) // ["3", "4", "5"]
zrzka
  • 20,249
  • 5
  • 47
  • 73
3

the last two items of an array in Swift

EDIT: first checks that myArray.count >= 2

let myArray2:Array? = myArray.count >= 2 ? [myArray[myArray.count-2], myArray[myArray.count-1]] : nil

Here it is wrapped in a function which takes the array and either returns an array containing the last two or else returns nil if the passed array does not contain at least two items.

func getLastTwo(myArray:[String]) -> [String]? {
        return myArray.count >= 2 ? [myArray[myArray.count-2], myArray[myArray.count-1]] : nil
    }
simons
  • 2,374
  • 2
  • 18
  • 20
  • Thanks for your answer, but that's not safe if the array is not guaranteed to have at least two items in it. – zekel Jun 23 '15 at 19:40
3
let items = [0, 2, 5, 3, 7, 6, 9, 10]
let count = items.count
let last2 = items[count - 2 ..< count] // [9, 10]
2

I doubt it's going to make you that much happier, but the math is certainly simpler:

func getLastTwo(array: [String]) -> [String] {
    if array.count <= 1 {
        return array
    } else {
        return array.reverse()[0...1].reverse()
    }
}

Note that reverse() is lazy, so this isn't particularly expensive.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
-1

Swift4 solution:

let oneArray = ["uno"]
let twoArray = ["uno", "dos"]
let threeArray = ["uno", "dos", "tres"]

let arr1 = threeArray.suffix(from: threeArray.count-2) // ["dos", "tres"]

Other examples to clarify the functionality of Swift's built in function func suffix(from start: Int) -> ArraySlice<Element> are...

let arr2 = oneArray.suffix(from: 0) // ["uno"]
let arr3 = twoArray.suffix(from: 0) // ["uno", "dos"]
let arr4 = twoArray.suffix(from: 1) // ["dos"]
let arr5 = threeArray.suffix(from: 1) // ["dos", "tres"]
let arr6 = threeArray.suffix(from: 2) // ["tres"]
iKK
  • 6,394
  • 10
  • 58
  • 131