3

I want to find the index of the last occurrence of a character in a String. For example if my string is "google.com/program/test" and I want to find the letter /, I want my function to return 18 because that is the last occurrence of / in that string. I have tried to follow Finding index of character in Swift String and have also tried to implement a means to loop through the string and simply find the last index of the String that has my desired character but the advance() function seems to complain about getting an Integer.

var strCount = 0
var lastSlashIndex = 0
for i in urlAsString {
  if i == "/"{
    if lastSlashIndex < strCount{
      lastSlashIndex = strCount
    }
  }
  strCount++
}

var endOfString = strCount - lastSlash
//Cannot seem to use the advance function to get the remaining substring
var fileName = urlAsString.substringFromIndex(advance(lastSlashIndex, endOfString))

Can't seem to figure this out, any help would be appreciated.

EDIT: This problem is not ment to be specific to just the '/' character. For example, if the string is "abbccd" and I'm looking for the letter 'c', then I want to return the index 4 because that is the last index in which 'c' occurs in.

Community
  • 1
  • 1
user1871869
  • 3,317
  • 13
  • 56
  • 106
  • Given this specific example, your question is a bit of an XY problem. – nhgrif Apr 26 '15 at 23:12
  • Sorry, I didn't mean to make it so specific. I want to simply find the last occurrence of a particular character. It can be any character. For example, if the string is "abbccd" and I'm looking for the letter 'c', then I want to return the index 4 because that is the last index in which c occurs in. @nhgrif – user1871869 Apr 26 '15 at 23:13
  • Is that *really* what you want? Or are you actually trying to find the file name out of a string that looks like a file path? There's a *very* simple one-liner for doing this... – nhgrif Apr 26 '15 at 23:15
  • @nhgrif well, my problem is this. I have a link that looks something like this: `https://www.dropbox.com/s/s73824fsda/FileName` and I want to just parse out the `FileName` portion of the link so I can use it in my program. If the one-liner works for this, I would love to see it! – user1871869 Apr 26 '15 at 23:18

5 Answers5

6

Rather than answering the question in the title, I'm taking a look at your example code and assuming this is an XY problem. If you just need to remove the last path component (or retrieve just the last path component), there are existing methods on NSString which make this very easy.

If all you want is the file name extracted out of a path:

let path = "/tmp/scratch.tiff"
let fileName = path.lastPathComponent

This works even when path looks like some sort of URL.

let path = "https://www.dropbox.com/s/s73824fsda/FileName.ext"
let fileName = path.lastPathComponent
println(fileName)

This prints:

FileName.ext

As a note, this works whether or not the last path component has any sort of extension or whatever. lastPathComponent just returns everything after the last / character (or if there is no /, the whole string).

NSString actually defines quite a lot of methods explicitly for working with file paths (and these all work on Swift's String). I recommend taking a look at the official documentation. NSString has a whole section of methods called "Working with Paths"

nhgrif
  • 61,578
  • 25
  • 134
  • 173
  • 1
    The documentation for `lastPathComponent` says `Note that this method only works with file paths (not, for example, string representations of URLs).` – Hyperbole Jul 17 '15 at 02:08
2

If you wanted to find the first occurrence of a character, you could use the find function, so one option is to write a reverse-find equivalent, modeled on the regular find, and then use that:

func rfind
  <C: CollectionType
       where C.Generator.Element: Equatable,
             // one extra constraint needed - the ability
             // to iterate backwards through the collection
             C.Index: BidirectionalIndexType>
  (domain: C, value: C.Generator.Element) -> C.Index? {

    // the wrapping of the indices in reverse() is really
    // the only difference between this and the regular find
    for idx in reverse(indices(domain)) {
        if domain[idx] == value {
            return idx
        }
    }
    return nil
}

Given this, it’s easy to use it to find the substring separated by the last occurrence of a character:

let path = "/some/path/to/something"

if let firstSlash = rfind(path, "/") {
    let file = path[firstSlash.successor()..<path.endIndex]
    println(file)  // prints “something”
}

Of course, you could just write the loop directly in your code if you don’t want to go to the trouble of defining the generic function.

Note, while strings in Swift aren’t random-access via integers, it doesn’t really matter since nowhere do you need to know that the last slash is the nth character, just the index of where it is.

If you want to assume absence of a slash means take the entire string as a filename you could do:

let start = find(path, "/")?.successor() ?? path.startIndex
let file = path[start..<path.endIndex]
Airspeed Velocity
  • 40,491
  • 8
  • 113
  • 118
  • This answer is great if we're looking to do these sorts of operations based on basically any character other than `/`. But for `/`, there's a whole set of methods designed for working with file paths. – nhgrif Apr 26 '15 at 23:42
  • Indeed, but I chose to interpret the question literally :) primarily because the question “how do I strip the path from a filename” is almost certainly a dupe... – Airspeed Velocity Apr 26 '15 at 23:43
2

@Duncan C suggested the rangeOfString function.

Here's an example of what that might look like in Swift 2.0:

func lastIndexOf(s: String) -> Int? {
    if let r: Range<Index> = self.rangeOfString(s, options: .BackwardsSearch) {
        return self.startIndex.distanceTo(r.startIndex)
    }

     return Optional<Int>()
}

Tests

func testStringLastIndexOf() {
    let lastIndex = "google.com/program/test".lastIndexOf("/")

    XCTAssertEqual(lastIndex, 18)
}

func testStringLastIndexOfNotFound() {
    let lastIndex = "google.com".lastIndexOf("/")

    XCTAssertEqual(lastIndex, nil);
}
Mark Moeykens
  • 15,915
  • 6
  • 63
  • 62
0

@nhgrif is probably right that this is an XY problem and what you really want to do is to parse a path or an URL, and there are built-in methods in NSString and NSURL to do that for you.

If you ARE Talking about general purpose string parsing, there are also methods for that.

By far the easiest thing to do is to use the NSString method rangeOfString:options: One of the option values is BackwardsSearch. So you'd pass it an options value of BackwardsSearch. You'd get back the range of the last occurrence of your search string.

I've seen some Swift string methods that use "Swift-ified" versions of range, and am not clear on what NSString methods have native Swift variants. (I'm learning Swift myself.)

Duncan C
  • 128,072
  • 22
  • 173
  • 272
0

The following accomplishes this task using an extension that can take a string of your choosing to locate. If it is not available, then you get a nil value back you can handle.

extension String {
    func lastOccurrenceOfString(string: String) -> String.Index? {
        let characterSet = CharacterSet(charactersIn: string)
        if let range = rangeOfCharacter(from: characterSet, options: .backwards) {
            let offsetBy = distance(from: startIndex, to: range.upperBound)

            return index(startIndex, offsetBy: offsetBy)
        }

        return nil
    }
}

And the following shows this in use:

import UIKit

class ViewController: UIViewController {
    let testString = "google.com/program/test"

    override func viewDidLoad() {
        super.viewDidLoad()

        getIndex(of: "/") // 19
        getIndex(of: ".") // 7
        getIndex(of: "z") // N/A
        getIndex(of: "me") // 21, e appears before m
    }

    func getIndex(of string: String) {

        let slashIndex = testString.lastOccurrenceOfString(string: string)
        print(slashIndex?.encodedOffset ?? "N/A")
    }
}

extension String {
    func lastOccurrenceOfString(string: String) -> String.Index? {
        let characterSet = CharacterSet(charactersIn: string)
        if let range = rangeOfCharacter(from: characterSet, options: .backwards) {
            let offsetBy = distance(from: startIndex, to: range.upperBound)

            return index(startIndex, offsetBy: offsetBy)
        }

        return nil
    }
}
CodeBender
  • 35,668
  • 12
  • 125
  • 132