1

There is a removeFirst(_:) method on String. However, the documentation seems very generic and doesn't mention anything about being specific to a string:

k: The number of elements to remove from the collection. k must be greater than or equal to zero and must not exceed the number of elements in the collection.

var bugs = ["Aphid", "Bumblebee", "Cicada", "Damselfly", "Earwig"]
bugs.removeFirst(3)
print(bugs)
// Prints "["Damselfly", "Earwig"]"

In fact, it looks very similar to the Array or Collection documentation of removeFirst(_:).

Since there are at least 6 different ways to get the character count in Swift, it makes it difficult to know which count I should use for k.

If I want to create a method such as string.removePrefix("foo"), which of the 6 character counts should I use?

Senseful
  • 86,719
  • 67
  • 308
  • 465

2 Answers2

5

removeFirst(_:) is a method of the RangeReplaceableCollection protocol, and for any collection, count gives the number of its elements. So for any instance a of a RangeReplaceableCollection type, the argument k passed to

a.removeFirst(k)

must be greater than or equal to zero, and less than or equal to a.count.

This applies to Array, String (which is a collection of Character) and all other “range replaceable collection” types:

// Array:
var arr = [1, 2, 3, 4]
arr.removeFirst(k)   // 0 <= k <= arr.count

// String:
var str = "‍‍‍"
str.removeFirst(k)   // 0 <= k <= str.count

// Unicode scalar view of a string:
var u = "‍‍‍".unicodeScalars
u.removeFirst(k)   // 0 <= k <= u.count

Having said that, I would implement the method as

extension String {
    func removingPrefix(_ prefix: String) -> String? {
        guard let range = range(of: prefix, options: .anchored) else {
            return nil
        }
        return String(self[range.upperBound...])
    }
}

to avoid unnecessary index/offset conversions.

Senseful
  • 86,719
  • 67
  • 308
  • 465
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thanks for the detailed answer! Just wondering: how do you find out that removeFirst is part of RangeReplaceableCollection? When I jump to definition on String, it looks like it's implemented directly on String. – Senseful Aug 23 '18 at 20:23
  • @Senseful: Actually it is a method of `RangeReplaceableCollection ` *and* a (restricted) method of `Collection`. `String` may have its own implementation or use the default one, unfortunately that is not apparent (to me) from the documentation page. I *assumed* that it is a protocol extension method, and then grep'ed in the source code. – Martin R Aug 23 '18 at 21:11
0

It appears it expects you to use string.count.

Playground test:

func test(_ string: String, _ desc: String? = nil) {
  var s = string
  s.removeFirst(1)
  print("\(string) -> \(s)")
  assert(string.count == s.count + 1)
}

test("abc")
test("❌")
test("")
test("☾test")
test("‍‍‍")
test("\u{200d}\u{200d}\u{200d}")
test("")
test("\u{1F468}")
test("‍♀️‍♂️")
test("你好吗")
test("مرحبا", "Arabic word")
test("م", "Arabic letter")
test("שלום", "Hebrew word")
test("ם", "Hebrew letter")

Output:

abc -> bc
❌ -> 
 -> 
☾test -> test
‍‍‍ -> 
‍‍‍ -> 
 -> 
 -> 
‍♀️‍♂️ -> ‍♀️‍♂️
你好吗 -> 好吗
مرحبا -> رحبا
م -> 
שלום -> לום
ם -> 

So you could create:

extension String {

  /// Removes a prefix from a string, if it exists.
  ///
  /// - Parameter prefix: The prefix to attempt to remove.
  /// - Returns: The string without the prefix, or `nil` if the prefix didn't exist.
  func removingPrefix(_ prefix: String) -> String? {
    guard starts(with: prefix) else {
      return nil
    }

    var result = self
    result.removeFirst(prefix.count)
    return result
  }
}

See Martin's answer for a better implementation

Senseful
  • 86,719
  • 67
  • 308
  • 465