How do you mutate the last element of an array?

My current answer is:

// For example, in the case that
// you want to add 1 to the last element of an array of integers...

var integers = [0, 1, 2, 3, 4, 5]

WAY_1: do {
  guard let last = integers.popLast() else { fatalError() }
  integers.append(last + 1)
}

WAY_2: do {
  integers[integers.endIndex - 1] = integers.last! + 1
}

However, I want something like this:

integers.last! += 1 // Impossible in current Swift.

because it's short and easy to understand.

Do you have any other ideas?

just this (somewhat shorter than in your example)?

integers[integers.count - 1] += 1

or invest in a writeable version of "last":

extension Array {
    var mutableLast: Element! {
        get {
            precondition(count > 0)
            return last
        }
        set {
            precondition(count > 0)
            self[count - 1] = newValue
        }
    }
}

Once in place and out of site in some utilities file of yours, the usage is almost as you wanted:

integers.mutableLast += 1
integers.mutableLast! += 1 // ditto

3 Likes

@tera Thank you.
That's absolutely a universal way.
I hope such kind of properties would be provided in standard library.

extension MutableCollection where Self: BidirectionalCollection {
    var mutableLast: Element {
        get { last! }
        set { self[index(before: endIndex)] = newValue }
    }
}

This works on other collections other than Array as well, as long as it has last and index(before:) (aka. BidirectionalCollection) and a mutable subscript setter for an index (aka. MutableCollection)

2 Likes

This made me wonder why last is not settable. Does anyone know? The code so far in this thread uses implicitly-unwrapped optionals but I think setting to nil should just remove the last element.

Is there any way to actually add just a setter? You can access "super.get" like this, with the code below, but you'll get disambiguation errors if you try to use it…

  var last: Element? {
    get {
      let `self`: some BidirectionalCollection<Element> = self
      return `self`.last
    }

…so you need some kind of stupid name instead, that is not last:

public extension BidirectionalCollection where Self: MutableCollection & RangeReplaceableCollection {
  /// Adds the `set` accessor to the `last` property.
  var last_set: Element? {
    get { last }
    set {
      switch (isEmpty, newValue) {
      case (false, let newValue?): self[index(before: endIndex)] = newValue
      case (false, nil): removeLast()
      case (true, let newValue?): append(newValue)
      case (true, nil): break
      }
    }
  }
}
var array = [1]
array.last_set? += 1
XCTAssertEqual(array.last, 2)
array.last_set = nil
array.last_set = nil
XCTAssert(array.isEmpty)
array.last_set = 0
XCTAssertEqual(array, [0])
1 Like

And, for symmetry, setting first to nil should trigger "array.remove(at: 0)" ?

We can have special "start / end" markers:

array.last = nil // compile time error, value is non optional
array.last = value // ok if array is not empty, runtime error if array is empty
array.first = nil // compile time error, value is non optional
array.first = value // ok if array not empty, runtime error if array is empty
array.start = nil // removes the first element, runtime error if array is empty
array.start = someValue // inserts a new element at zero
array.end = nil // removes the last element, runtime error if array is empty
array.end = someValue // appends a new element

OTOH, getters for start/end markers would make no sense. Which suggests they should be methods rather than vars. And "array.start = nil / val" is hardly nicer than "removeFirst" / "prepend(val)".

1 Like

You can't do this with methods:

collection.first? += 1
1 Like

Perhaps I wasn't clear. I see no problem of having first and last mutable vars to allow for "array.last += 1", etc. I do see a problem with an API where "last = value" either changes the last entry or appends depending upon if array is empty or not, or, equally, that I can append to array via "last" but not prepend to array via "first" → hence the above attempt to split the two concepts first/last and start/end - the latter used exclusively for the purposes of appending / prepending and shrinking from either side - but then came to the conclusion that in that case they should be just methods.

See Dictionary for a precedent. The methods mimic the accessors, but return the equivalent of oldValue.

https://developer.apple.com/documentation/swift/dictionary/updatevalue(_:forkey:)
https://developer.apple.com/documentation/swift/dictionary/removevalue(forkey:)