在Swift String中查找字符的索引


203

现在该承认失败了...

在Objective-C中,我可以使用类似以下内容的东西:

NSString* str = @"abcdefghi";
[str rangeOfString:@"c"].location; // 2

在Swift中,我看到了类似的内容:

var str = "abcdefghi"
str.rangeOfString("c").startIndex

...但是这给了我一个String.Index,可以用来将其下标回到原始字符串,但不能从中提取位置。

FWIW,其中String.Index有一个私有ivar _position,其中具有正确的值。我只是看不到它的暴露方式。

我知道我可以轻松地将其自己添加到String中。我对这个新API缺少的东西感到好奇。


这是一个GitHub项目,其中包含许多用于Swift字符串操作的扩展方法:github.com/iamjono/SwiftString
RenniePet 2016年

我发现的最佳实现是在这里:stackoverflow.com/a/32306142/4550651
卡洛斯·加西亚

您是否需要区分Unicode代码点和扩展字素簇?
Ben Leggiero

Answers:


248

您不是唯一找不到解决方案的人。

String没有实现RandomAccessIndexType。可能是因为它们启用了具有不同字节长度的字符。这就是为什么我们必须使用string.characters.countcountcountElements在Swift 1.x中)获取字符数的原因。这也适用于职位。的_position可能是一个索引字节的原始阵列,他们不希望公开这一点。本String.Index是为了保护我们从人物的中间访问字节。

这意味着您必须从String.startIndexString.endIndexString.Index实现BidirectionalIndexType)创建任何索引。可以使用successorpredecessor方法创建任何其他索引。

现在可以使用索引来帮助我们,它提供了一组方法(Swift 1.x中的函数):

斯威夫特4.x

let text = "abc"
let index2 = text.index(text.startIndex, offsetBy: 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let characterIndex2 = text.index(text.startIndex, offsetBy: 2)
let lastChar2 = text[characterIndex2] //will do the same as above

let range: Range<String.Index> = text.range(of: "b")!
let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)

斯威夫特3.0

let text = "abc"
let index2 = text.index(text.startIndex, offsetBy: 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let characterIndex2 = text.characters.index(text.characters.startIndex, offsetBy: 2)
let lastChar2 = text.characters[characterIndex2] //will do the same as above

let range: Range<String.Index> = text.range(of: "b")!
let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)

斯威夫特2.x

let text = "abc"
let index2 = text.startIndex.advancedBy(2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!
let lastChar2 = text.characters[index2] //will do the same as above

let range: Range<String.Index> = text.rangeOfString("b")!
let index: Int = text.startIndex.distanceTo(range.startIndex) //will call successor/predecessor several times until the indices match

斯威夫特1.x

let text = "abc"
let index2 = advance(text.startIndex, 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let range = text.rangeOfString("b")
let index: Int = distance(text.startIndex, range.startIndex) //will call succ/pred several times

使用String.Index它很麻烦,但是使用包装器按整数索引(请参阅https://stackoverflow.com/a/25152652/669586)很危险,因为它掩盖了实际索引的效率低下。

请注意,Swift索引实现存在一个问题,即为一个字符串创建的索引/范围不能可靠地用于其他字符串,例如:

斯威夫特2.x

let text: String = "abc"
let text2: String = "🎾🏇🏈"

let range = text.rangeOfString("b")!

//can randomly return a bad substring or throw an exception
let substring: String = text2[range]

//the correct solution
let intIndex: Int = text.startIndex.distanceTo(range.startIndex)
let startIndex2 = text2.startIndex.advancedBy(intIndex)
let range2 = startIndex2...startIndex2

let substring: String = text2[range2]

斯威夫特1.x

let text: String = "abc"
let text2: String = "🎾🏇🏈"

let range = text.rangeOfString("b")

//can randomly return nil or a bad substring 
let substring: String = text2[range] 

//the correct solution
let intIndex: Int = distance(text.startIndex, range.startIndex)    
let startIndex2 = advance(text2.startIndex, intIndex)
let range2 = startIndex2...startIndex2

let substring: String = text2[range2]  

1
尴尬的想法可能是这样,这似乎是答案。希望这两个范围函数可以在最终版本发布之前的某个时间将其纳入文档。
Matt Wilding 2014年

是什么类型rangevar range = text.rangeOfString("b")
zaph

5
@Zaph每个人Collection都有一个typealias IndexType。对于数组,它定义为Int,因为String它定义为String.Index。数组和字符串都可以使用范围(以创建子数组和子字符串)。范围是一种特殊类型Range<T>。对于字符串,Range<String.Index>对于数组Range<Int>
Sulthan 2014年

1
在中Swift 2.0distance(text.startIndex, range.startIndex)成为text.startIndex.distanceTo(range.startIndex)
superarts.org 2015年

1
@devios String就像NSString在Foundation中一样有一个名为的方法hasPrefix(_:)
苏珊(Sulthan)2013年

86

Swift 3.0使它更加冗长:

let string = "Hello.World"
let needle: Character = "."
if let idx = string.characters.index(of: needle) {
    let pos = string.characters.distance(from: string.startIndex, to: idx)
    print("Found \(needle) at position \(pos)")
}
else {
    print("Not found")
}

延期:

extension String {
    public func index(of char: Character) -> Int? {
        if let idx = characters.index(of: char) {
            return characters.distance(from: startIndex, to: idx)
        }
        return nil
    }
}

Swift 2.0中,这变得更加容易:

let string = "Hello.World"
let needle: Character = "."
if let idx = string.characters.indexOf(needle) {
    let pos = string.startIndex.distanceTo(idx)
    print("Found \(needle) at position \(pos)")
}
else {
    print("Not found")
}

延期:

extension String {
    public func indexOfCharacter(char: Character) -> Int? {
        if let idx = self.characters.indexOf(char) {
            return self.startIndex.distanceTo(idx)
        }
        return nil
    }
}

Swift 1.x实现:

对于纯Swift解决方案,可以使用:

let string = "Hello.World"
let needle: Character = "."
if let idx = find(string, needle) {
    let pos = distance(string.startIndex, idx)
    println("Found \(needle) at position \(pos)")
}
else {
    println("Not found")
}

作为对以下内容的扩展String

extension String {
    public func indexOfCharacter(char: Character) -> Int? {
        if let idx = find(self, char) {
            return distance(self.startIndex, idx)
        }
        return nil
    }
}

1
字符已弃用!!
Shivam Pokhriyal

23
extension String {

    // MARK: - sub String
    func substringToIndex(index:Int) -> String {
        return self.substringToIndex(advance(self.startIndex, index))
    }
    func substringFromIndex(index:Int) -> String {
        return self.substringFromIndex(advance(self.startIndex, index))
    }
    func substringWithRange(range:Range<Int>) -> String {
        let start = advance(self.startIndex, range.startIndex)
        let end = advance(self.startIndex, range.endIndex)
        return self.substringWithRange(start..<end)
    }

    subscript(index:Int) -> Character{
        return self[advance(self.startIndex, index)]
    }
    subscript(range:Range<Int>) -> String {
        let start = advance(self.startIndex, range.startIndex)
            let end = advance(self.startIndex, range.endIndex)
            return self[start..<end]
    }


    // MARK: - replace
    func replaceCharactersInRange(range:Range<Int>, withString: String!) -> String {
        var result:NSMutableString = NSMutableString(string: self)
        result.replaceCharactersInRange(NSRange(range), withString: withString)
        return result
    }
}

7
考虑过要这样做,但是我认为它隐藏了字符串访问的语义是一个问题。想象一下创建一个用于访问链接列表的API,该API看起来就像数组的API。人们想编写极其低效的代码。
Erik Engheim 2014年

16

我已经找到了swift2的解决方案:

var str = "abcdefghi"
let indexForCharacterInString = str.characters.indexOf("c") //returns 2

当str =“ abcdefgchi”时,索引将是什么?
Vatsal Shukla,

10

斯威夫特5.0

public extension String {  
  func indexInt(of char: Character) -> Int? {
    return firstIndex(of: char)?.utf16Offset(in: self)
  }
}

迅捷4.0

public extension String {  
  func indexInt(of char: Character) -> Int? {
    return index(of: char)?.encodedOffset        
  }
}

return index(of:element).map {target.distance(from:startIndex,to:$ 0)}
frogcjn

8

我不确定如何从String.Index中提取位置,但是如果您愿意依靠某些Objective-C框架,则可以桥接到Objective-C并像以前一样进行操作。

"abcdefghi".bridgeToObjectiveC().rangeOfString("c").location

似乎某些NSString方法尚未(或可能不会)移植到String。包含也浮现在脑海。


实际上看来,访问返回值的location属性足以使编译器推断出NSString类型,因此bridgeToObjectiveC()不需要调用。我的问题似乎仅在调用rangeOfString先前存在的Swift String 时才显现出来。似乎是API问题...
Matt Wilding 2014年

那很有意思。在这些情况下,我不知道这是推断出来的。好吧,当它已经是字符串时,您可以随时使用网桥。
康纳2014年

8

这是一个干净的String扩展,它回答了这个问题:

斯威夫特3:

extension String {
    var length:Int {
        return self.characters.count
    }

    func indexOf(target: String) -> Int? {

        let range = (self as NSString).range(of: target)

        guard range.toRange() != nil else {
            return nil
        }

        return range.location

    }
    func lastIndexOf(target: String) -> Int? {



        let range = (self as NSString).range(of: target, options: NSString.CompareOptions.backwards)

        guard range.toRange() != nil else {
            return nil
        }

        return self.length - range.location - 1

    }
    func contains(s: String) -> Bool {
        return (self.range(of: s) != nil) ? true : false
    }
}

Swift 2.2:

extension String {    
    var length:Int {
        return self.characters.count
    }

    func indexOf(target: String) -> Int? {

        let range = (self as NSString).rangeOfString(target)

        guard range.toRange() != nil else {
            return nil
        }

        return range.location

    }
    func lastIndexOf(target: String) -> Int? {



        let range = (self as NSString).rangeOfString(target, options: NSStringCompareOptions.BackwardsSearch)

        guard range.toRange() != nil else {
            return nil
        }

        return self.length - range.location - 1

    }
    func contains(s: String) -> Bool {
        return (self.rangeOfString(s) != nil) ? true : false
    }
}

7

您还可以像这样在单个字符串中找到字符的索引,

extension String {

  func indexes(of character: String) -> [Int] {

    precondition(character.count == 1, "Must be single character")

    return self.enumerated().reduce([]) { partial, element  in
      if String(element.element) == character {
        return partial + [element.offset]
      }
      return partial
    }
  }

}

在[String.Distance]中给出结果,即。[Int],像

"apple".indexes(of: "p") // [1, 2]
"element".indexes(of: "e") // [0, 2, 4]
"swift".indexes(of: "j") // []

5

如果要使用熟悉的NSString,则可以显式声明:

var someString: NSString = "abcdefghi"

var someRange: NSRange = someString.rangeOfString("c")

我不确定如何在Swift中执行此操作。


1
这当然有效,并且似乎编译器在为您推断NSString类型方面非常积极。我真的希望有一种纯粹的Swift方式,因为这似乎是一个足够普遍的用例。
Matt Wilding 2014年

是的,我环顾四周,但看不到。他们有可能专注于ObjC不支持的领域,因为它们可以填补这些空白而不会损失太多的功能。只是大声思考:)
Logan 2014年

4

如果您想知道一个字符在字符串中作为int值的位置,请使用以下命令:

let loc = newString.range(of: ".").location

斯威夫特5 :value of type 'Range<String.Index>?' has no member 'location'
Neph

3

我知道这很旧,并且已经接受了答案,但是您可以使用以下代码在几行代码中找到字符串的索引:

var str : String = "abcdefghi"
let characterToFind: Character = "c"
let characterIndex = find(str, characterToFind)  //returns 2

有关Swift字符串的其他一些重要信息,请参见Swift中的字符串


find在Swift 3
AamirR'Feb

2

这对我有用

var loc = "abcdefghi".rangeOfString("c").location
NSLog("%d", loc);

这也起作用

var myRange: NSRange = "abcdefghi".rangeOfString("c")
var loc = myRange.location
NSLog("%d", loc);

两者似乎都以某种方式回退了NSString的行为。在我的操场上,我在为字符串使用中间变量。var str = "abcdef"; str.rangeOfString("c").location引发有关String.Index没有名为位置的成员的错误...
Matt Wilding 2014年

1
起作用是因为“字符串”是NSString而不是Swift的字符串
gderaco 2014年

2

与Objective-C中的NSString相比,Swift中的变量类型String包含不同的功能。正如苏尔坦所说,

Swift String没有实现RandomAccessIndex

您可以做的是将String类型的变量向下转换为NSString(在Swift中有效)。这将使您可以访问NSString中的函数。

var str = "abcdefghi" as NSString
str.rangeOfString("c").locationx   // returns 2

2

如果您考虑一下,实际上您实际上并不需要该位置的确切Int版本。如果需要,Range甚至String.Index足以再次将子字符串取出:

let myString = "hello"

let rangeOfE = myString.rangeOfString("e")

if let rangeOfE = rangeOfE {
    myString.substringWithRange(rangeOfE) // e
    myString[rangeOfE] // e

    // if you do want to create your own range
    // you can keep the index as a String.Index type
    let index = rangeOfE.startIndex
    myString.substringWithRange(Range<String.Index>(start: index, end: advance(index, 1))) // e

    // if you really really need the 
    // Int version of the index:
    let numericIndex = distance(index, advance(index, 1)) // 1 (type Int)
}

对我来说最好的答案是func indexOf(target:String)-> Int {return(self as NSString).rangeOfString(target)
.location

2

最简单的方法是:

Swift 3中

 var textViewString:String = "HelloWorld2016"
    guard let index = textViewString.characters.index(of: "W") else { return }
    let mentionPosition = textViewString.distance(from: index, to: textViewString.endIndex)
    print(mentionPosition)

1

字符串是NSString的桥接类型,因此添加

import Cocoa

到您的swift文件,并使用所有“旧”方法。


1

从思想上讲,这可以称为反转。您会发现世界是圆形的而不是平坦的。“您真的不需要知道角色的索引就可以使用它来做事。” 作为一名C程序员,我也觉得很难接受!您的行“让索引= letters.characters.indexOf(“ c”)!“ 本身就足够了。例如,删除c可以使用...(操场上粘贴)

    var letters = "abcdefg"
  //let index = letters.rangeOfString("c")!.startIndex //is the same as
    let index = letters.characters.indexOf("c")!
    range = letters.characters.indexOf("c")!...letters.characters.indexOf("c")!
    letters.removeRange(range)
    letters

但是,如果要使用索引,则需要返回实际的INDEX而不是Int,因为Int值需要任何实际使用的附加步骤。这些扩展返回一个索引,一个特定字符的计数以及此操场插件功能代码将演示的范围。

extension String
{
    public func firstIndexOfCharacter(aCharacter: Character) -> String.CharacterView.Index? {

        for index in self.characters.indices {
            if self[index] == aCharacter {
                return index
            }

        }
        return nil
    }

    public func returnCountOfThisCharacterInString(aCharacter: Character) -> Int? {

        var count = 0
        for letters in self.characters{

            if aCharacter == letters{

                count++
            }
        }
        return count
    }


    public func rangeToCharacterFromStart(aCharacter: Character) -> Range<Index>? {

        for index in self.characters.indices {
            if self[index] == aCharacter {
                let range = self.startIndex...index
                return range
            }

        }
        return nil
    }

}



var MyLittleString = "MyVery:important String"

var theIndex = MyLittleString.firstIndexOfCharacter(":")

var countOfColons = MyLittleString.returnCountOfThisCharacterInString(":")

var theCharacterAtIndex:Character = MyLittleString[theIndex!]

var theRange = MyLittleString.rangeToCharacterFromStart(":")
MyLittleString.removeRange(theRange!)

1

Swift 4完整解决方案:

OffsetIndexableCollection(使用Int索引的字符串)

https://github.com/frogcjn/OffsetIndexableCollection-String-Int-Indexable-

let a = "01234"

print(a[0]) // 0
print(a[0...4]) // 01234
print(a[...]) // 01234

print(a[..<2]) // 01
print(a[...2]) // 012
print(a[2...]) // 234
print(a[2...3]) // 23
print(a[2...2]) // 2

if let number = a.index(of: "1") {
    print(number) // 1
    print(a[number...]) // 1234
}

if let number = a.index(where: { $0 > "1" }) {
    print(number) // 2
}

1

扩展字符串{

//Fucntion to get the index of a particular string
func index(of target: String) -> Int? {
    if let range = self.range(of: target) {
        return characters.distance(from: startIndex, to: range.lowerBound)
    } else {
        return nil
    }
}
//Fucntion to get the last index of occurence of a given string
func lastIndex(of target: String) -> Int? {
    if let range = self.range(of: target, options: .backwards) {
        return characters.distance(from: startIndex, to: range.lowerBound)
    } else {
        return nil
    }
}

}


1

斯威夫特5

查找子串的索引

let str = "abcdecd"
if let range: Range<String.Index> = str.range(of: "cd") {
    let index: Int = str.distance(from: str.startIndex, to: range.lowerBound)
    print("index: ", index) //index: 2
}
else {
    print("substring not found")
}

查找字符索引

let str = "abcdecd"
if let firstIndex = str.firstIndex(of: "c") {
    let index: Int = str.distance(from: str.startIndex, to: firstIndex)
    print("index: ", index)   //index: 2
}
else {
    print("symbol not found")
}


0

使用Swift 2获取字符串中子字符串的索引:

let text = "abc"
if let range = text.rangeOfString("b") {
   var index: Int = text.startIndex.distanceTo(range.startIndex) 
   ...
}

0

在迅速2.0

var stringMe="Something In this.World"
var needle="."
if let idx = stringMe.characters.indexOf(needle) {
    let pos=stringMe.substringFromIndex(idx)
    print("Found \(needle) at position \(pos)")
}
else {
    print("Not found")
}

0
let mystring:String = "indeep";
let findCharacter:Character = "d";

if (mystring.characters.contains(findCharacter))
{
    let position = mystring.characters.indexOf(findCharacter);
    NSLog("Position of c is \(mystring.startIndex.distanceTo(position!))")

}
else
{
    NSLog("Position of c is not found");
}

0

我跟着玩

extension String {
    func allCharactes() -> [Character] {
         var result: [Character] = []
         for c in self.characters {
             result.append(c)
         }
         return 
    }
}

直到我理解所提供的,这只是字符数组

let c = Array(str.characters)

这有什么作用?您的第一个代码块比第二个代码块如何好?
Ben Leggiero

0

如果只需要一个字符的索引,最简单,快速的解决方案(如Pascal所指出的)是:

let index = string.characters.index(of: ".")
let intIndex = string.distance(from: string.startIndex, to: index)

字符现在贬值......可悲的是,即使这样的作品
user3069232

0

关于将a String.Index变成an 的主题Int,此扩展名对我有用:

public extension Int {
    /// Creates an `Int` from a given index in a given string
    ///
    /// - Parameters:
    ///    - index:  The index to convert to an `Int`
    ///    - string: The string from which `index` came
    init(_ index: String.Index, in string: String) {
        self.init(string.distance(from: string.startIndex, to: index))
    }
}

与该问题有关的示例用法:

var testString = "abcdefg"

Int(testString.range(of: "c")!.lowerBound, in: testString)     // 2

testString = "🇨🇦🇺🇸🇩🇪👩‍👩‍👧‍👦\u{1112}\u{1161}\u{11AB}"

Int(testString.range(of: "🇨🇦🇺🇸🇩🇪")!.lowerBound, in: testString) // 0
Int(testString.range(of: "👩‍👩‍👧‍👦")!.lowerBound, in: testString)    // 1
Int(testString.range(of: "한")!.lowerBound, in: testString)    // 5

重要:

如您所知,它对扩展字素簇和连接字符的分组方式不同于String.Index。当然,这就是我们拥有的原因String.Index。您应该记住,此方法将群集视为单个字符,这更接近于正确。如果您的目标是按Unicode代码点分割字符串,那么这不是您的解决方案。


0

Swift 2.0中,以下函数在给定字符之前返回子字符串。

func substring(before sub: String) -> String {
    if let range = self.rangeOfString(sub),
        let index: Int = self.startIndex.distanceTo(range.startIndex) {
        return sub_range(0, index)
    }
    return ""
}

0

您可以使用以下命令在字符串中找到字符的索引号:

var str = "abcdefghi"
if let index = str.firstIndex(of: "c") {
    let distance = str.distance(from: str.startIndex, to: index)
    // distance is 2
}

0

以我的观点,了解逻辑本身的更好方法如下

 let testStr: String = "I love my family if you Love us to tell us I'm with you"
 var newStr = ""
 let char:Character = "i"

 for value in testStr {
      if value == char {
         newStr = newStr + String(value)
   }

}
print(newStr.count)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.