2

With the new ways of handling string in Swift 4, I'm trying to wrap my head around how to write the equivalent of the Mid function from other languages (visual basic, etc), so that

let testString = "0123456"
print Mid(testString, 2,4)  // "1234"  (or "2345" would work too)

This question is the same idea, but everything there predates Swift 4. If the answer there by jlert is still the best way to do things in Swift 4, that works, although it seems like so much has changed that the best practice to do this may have changed as well.

Tad Donaghe
  • 6,625
  • 1
  • 29
  • 64
ConfusionTowers
  • 911
  • 11
  • 34

4 Answers4

2

One way to do it is with a combination of dropFirst and prefix and then use String to convert the result back to String:

let testString = "0123456"

func mid(_ str: String, _ low: Int, _ count: Int) -> String {
    return String(str.dropFirst(low).prefix(count))
}

print(mid(testString, 2,4))  // 2345

dropFirst and prefix are forgiving and won't crash if enough letters are not there. They will just give a truncated result. Depending on how you define the function, this is a perfectly acceptable implementation.


Another approach would be to use array subscripting. If you do it that way, you need to check inputs to avoid Array index out of range fatal errors:

func mid(_ str: String, _ low: Int, _ count: Int) -> String? {
    guard low >= 0,
          count >= 0,
          low < str.count,
          low + count <= str.count
        else { return nil }

    return String(Array(str)[low ..< low + count])
}

let testString = "abcdefghi"
if let result = mid(testString, 2, 4) {
    print(result)  // cdef
}
vacawama
  • 150,663
  • 30
  • 266
  • 294
2

You can do:

extension String {
    func mid(_ startOffset: Int, _ length: Int) -> Substring {
        let start = index(startIndex, offsetBy: startOffset, limitedBy: endIndex) ?? endIndex
        let end = index(start, offsetBy: length, limitedBy: endIndex) ?? endIndex

        return self[start ..< end]
    }
}

let string = "0123456"

let result = string.mid(2, 4)  // 2345

Note, this returns a Substring, which enjoys memory efficiency. But if you want a String, you can do :

let result = String(string.mid(2, 4))

(Or you can incorporate this in your mid function.)

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • I was about to post my answer using the same approach. but instead of using limited by I preferred to use precondition – Leo Dabus Oct 18 '17 at 01:54
  • Yeah, I see yours. I decided to adopt the behavior of most VB-style `Mid` implementations that are forgiving re ranges e.g. (`string.mid(2, 999)` would return `"23456"`) rather than throwing/reporting errors. – Rob Oct 18 '17 at 01:56
  • I did implement also it returning non optional and using nil coalescing operator to limit the indexes and passing the endIndex instead – Leo Dabus Oct 18 '17 at 01:58
  • What happen in VB if you pass a position after the endIndex? Does it return just an empty string? – Leo Dabus Oct 18 '17 at 02:01
  • 1
    Yeah, the nil coalescing operator is more concise. Nice suggestion that I've incorporated. Re starting position > endIndex, VB silently accepts it, returning `""`. It only throws errors if you supply a value less than 1. (Note I deviate from VB 1-based starting index, in favor of Swifty 0-based index.) – Rob Oct 18 '17 at 02:05
  • btw I have updated my string subscript to support partial range I thought you would be interested https://stackoverflow.com/questions/24092884/get-nth-character-of-a-string-in-swift-programming-language/38215613#38215613 – Leo Dabus Oct 18 '17 at 02:19
1

I would take a different approach using String.Index and return a Substring instead of a new String object. You can also add precondition to restrict improper use of that method:

func mid(_ string: String, _ positon: Int, length: Int) -> Substring {
    precondition(positon < string.count, "invalid position")
    precondition(positon + length <=  string.count, "invalid substring length")
    let lower = string.index(string.startIndex, offsetBy: positon)
    let upper = string.index(lower, offsetBy: length)
    return string[lower..<upper]
}

let testString = "0123456"
mid(testString, 2, length: 4)  // "2345"

Another option would be creating that method as a string extension:

extension String {
    func mid(_ positon: Int, length: Int) -> Substring {
        precondition(positon < count, "invalid position")
        precondition(positon + length <=  count, "invalid substring length")
        let lower = index(startIndex, offsetBy: positon)
        let upper = index(lower, offsetBy: length)
        return self[lower..<upper]
    }
}

let testString = "0123456"
testString.mid(2, length: 4)   // "2345"
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
1

iOS 14, Swift 5... same thing as vacawama excellent answer, but as an extension, so it's even less code :)

extension String {
  func mid(_ low: Int, _ count: Int) -> String {
    return String(self.dropFirst(low).prefix(count))
  }
}

Use it like this.

let pString = "01234567"
for i in 0 ..< 8 {
  print("\(i) \(pString.mid(i,1)")
}

Prints out your string to the console, one character at a time

user3069232
  • 8,587
  • 7
  • 46
  • 87