Find the last occurrence of a character in a String?
I want to find the index of the last occurrence of a character in a String. For example, if my is a string "google.com/program/test"
and I want to find a letter /
, I want my function to return 18
because this is the last insert /
on that line. I have tried Finding the index of a character in a Swift String , and also tried to implement a facility to loop through the string and just find the last index of the String that my desired character has, but the function advance()
seems to complain about getting 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 make it clear, any help would be appreciated.
EDIT: This issue is not related to the specific character "/". For example, if a string "abbccd"
and I am looking for a letter 'c'
, then I want to return the index 4
because that is the last index that is 'c'
present.
source to share
Instead of answering the question in the title, I'll go over your example code and assume it's an XY issue. If you just need to remove the last component of the path (or get only the last component of the path), there are existing methods on NSString
that make it very easy.
If all you want is the filename extracted from the path:
let path = "/tmp/scratch.tiff"
let fileName = path.lastPathComponent
This works even when path
it looks like some kind of URL.
let path = "https://www.dropbox.com/s/s73824fsda/FileName.ext"
let fileName = path.lastPathComponent
println(fileName)
Prints:
FileName.ext
As a side note, this works regardless of whether the last component of the path has any kind of extension or something. lastPathComponent
just returns everything after the last character /
(or if not /
, the whole string).
NSString
actually defines quite a few methods explicitly for working with file paths (and they all work with Swift String
). I recommend taking a look at the official documentation. NSString
has a whole section of methods called "Working with paths"
source to share
If you want to find the first occurrence of a character, you can use a function find
, so one option is to write the equivalent of a reverse search simulated on a regular basis, 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
}
With that in mind, it's easy to use it to find a 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 can just write the loop directly in your code if you don't want the problem solved when defining a generic function.
Note that while strings in Swift aren't randomly accessed via integers, it doesn't really matter since you don't need to know anywhere that the last slash is the nth character, just the index of where it is.
If you want to assume there is no forward slash, take the whole line as a filename you could do:
let start = find(path, "/")?.successor() ?? path.startIndex
let file = path[start..<path.endIndex]
source to share
@Duncan C suggested the rangeOfString function.
Here's an example of what 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);
}
source to share
@nhgrif is probably correct that this is an XY problem and what you really want to do is parse the path or url, and there are built-in methods for NSString and NSURL to do this for you.
If you're talking about parsing general purpose strings, there are methods for that as well.
In fact, the easiest way is to use the NSString method rangeOfString:options:
. One of the parameter values ββis BackwardsSearch. Thus, you will pass the BackwardsSearch parameter value to it. You will return the range of last occurrences of the search string.
I have seen some Swift string methods that use Swift-ified versions of the range, and it is not clear which NSString methods have native Swift variants. (I study Swift myself.)
source to share
The following accomplishes this task using an extension that can select the string you choose to search for. If not available, you will get a null value that you can process.
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 it shows the following:
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
}
}
source to share