9

I'm upgrading code from Swift 2 to Swift 3 and ran across this error:

wordcount.swift:7:5: error: value of type 'String' has no member 'enumerateSubstringsInRange' line.enumerateSubstringsInRange(range, options: .ByWords) {w,,,_ in

In Swift 2, this method comes from a String extension of which the compiler is aware.

I have not been able to locate this method in the Swift 3 library. It appears in the documentation for Foundation here:

https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/index.html#//apple_ref/occ/instm/NSString/enumerateSubstringsInRange:options:usingBlock:

My entire script is:

import Foundation

var counts = [String: Int]()

while let line = readLine()?.lowercased() {
    let range = line.characters.indices
    line.enumerateSubstringsInRange(range, options: .ByWords) {w,_,_,_ in
        guard let word = w else {return}
        counts[word] = (counts[word] ?? 0) + 1
    }
}

for (word, count) in (counts.sorted {$0.0 < $1.0}) {
    print("\(word) \(count)")
}

It works with Swift 2.2 (modulo the changes I have already made for Swift 3, such as lowercase -> lowercased and sort -> sorted) but fails to compile with Swift 3.

And very strangely, neither the Swift 3 command line compiler nor the Swift Migration assistant in XCode 8 Beta suggests a replacement, as it does for many other renamed methods. Perhaps enumerateSubstringsInRange is deprecated or its parameter names changed?

Ray Toal
  • 86,166
  • 18
  • 182
  • 232

1 Answers1

11

If you type str.enumerateSubstrings in a Playground, you'll see the following as a completion option:

enumerateSubstrings(in: Range<Index>, options: EnumerationOptions, body: (substring: String?, substringRange: Range<Index>, enclosingRange: Range<Index>, inout Bool) -> ())

In addition to addressing the new enumerateSubstrings(in:options:body:) syntax, you need to also change how you get the range for the string:

import Foundation

var counts = [String: Int]()

while let line = readLine()?.lowercased() {
    let range = line.startIndex ..< line.endIndex
    line.enumerateSubstrings(in: range, options: .byWords) {w,_,_,_ in
        guard let word = w else {return}
        counts[word] = (counts[word] ?? 0) + 1
    }
}

for (word, count) in (counts.sorted {$0.0 < $1.0}) {
    print("\(word) \(count)")
}
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • Fantastic. I should have guessed that was the new method, given that I've seen many fixes such as `joinWithSeparator(_:)` becoming `joined(separator:)`. – Ray Toal Jul 23 '16 at 21:52
  • Why does it have to be so complicated? Shouldn't it be easier than Obj-C, where you simply use `NSMakeRange(0, line.length)`? – Iulian Onofrei Nov 25 '17 at 13:29
  • 1
    @IulianOnofrei, I share your frustration. The designers of Swift have made Strings more capable than before, but working with them is a bit more difficult at times. – vacawama Nov 25 '17 at 14:46