Пошук індексу символів у Swift String


203

Пора визнати поразку ...

У Objective-C я міг би використовувати щось на кшталт:

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

У Свіфті я бачу щось подібне:

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

... але це просто дає мені знак String.Index, який я можу використовувати для підписки назад у початковий рядок, але не вилучати звідти місцеположення.

FWIW, що String.Indexмає приватний ivar, який називається _position, має правильне значення в ньому. Я просто не бачу, як це піддається.

Я знаю, що міг би легко додати це до String сам. Мені цікавіше, що мені не вистачає в цьому новому API.


Ось проект GitHub, який містить безліч методів розширення для маніпуляції з рядком Swift: github.com/iamjono/SwiftString
RenniePet

Найкраща реалізація, яку я знайшов, тут: stackoverflow.com/a/32306142/4550651
Carlos García

Чи потрібно розрізняти кодові точки Unicode та розширені кластери графеми?
Бен Леджієро

Відповіді:


248

Ви не єдиний, хто не зміг знайти рішення.

Stringне реалізує RandomAccessIndexType. Можливо, тому, що вони включають символи з різною довжиною байтів. Ось чому ми повинні використовувати string.characters.count( countабо countElementsв Swift 1.x), щоб отримати кількість символів. Це стосується і посад. Це _position, мабуть, індекс в необробленому масиві байтів, і вони не хочуть цього викривати. Це String.Indexпокликане захистити нас від доступу до байтів посеред символів.

Це означає, що будь-який індекс, який ви отримуєте, повинен бути створений з ( String.startIndexабо реалізується ). Будь-які інші індекси можна створити за допомогою або методів.String.endIndexString.IndexBidirectionalIndexTypesuccessorpredecessor

Тепер, щоб допомогти нам з індексами, існує набір методів (функції у 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)

Swift 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
Незграбно подумав, що це може бути, це, здається, відповідь. Сподіваємось, ці дві функції діапазону внесуть його в документацію десь до остаточного випуску.
Метт Вілдінг

Який тип rangeвvar range = text.rangeOfString("b")
zaph

5
@Zaph У кожного Collectionє typealias IndexType. Для масивів він визначається як Int, для Stringнього визначено як String.Index. І масиви, і рядки також можуть використовувати діапазони (для створення підмагістралей та підрядків). Асортимент - особливий тип Range<T>. Для рядків - це Range<String.Index>, для масивів Range<Int>.
Султан

1
В Swift 2.0, distance(text.startIndex, range.startIndex)стаєtext.startIndex.distanceTo(range.startIndex)
superarts.org

1
@devios String, точно так само, як NSStringу Фонді, має метод, званий hasPrefix(_:).
Султан

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
символи застаріли !!
Шивам Похріял

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 для масиву. Люди хотіли б написати жахливо неефективний код.
Ерік Енгхайм

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)
  }
}

Swift 4.0

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

індекс повернення (з: елемента) .map {target.distance (від: startIndex, до: $ 0)}
frogcjn

8

Я не впевнений, як витягнути позицію з String.Index, але якщо ви готові перейти на деякі рамки Objective-C, ви можете перейти до об'єкта-c і зробити це так само, як раніше.

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

Схоже, деякі методи NSString ще не були (або, можливо, не будуть) перенесені в String. Містить також приходить на думку.


Насправді здається, що доступу до властивості розташування повернутого значення достатньо, щоб компілятор мав висновок типу NSString, тому bridgeToObjectiveC()виклик не потрібен. Моя проблема, мабуть, виявляється лише під час виклику rangeOfStringраніше існуючих рядків Swift. Схоже, проблема API ...
Matt Wilding

це цікаво. Я не знав, що це робиться в тих випадках. Добре, коли це вже String, ви завжди можете використовувати міст.
Коннор

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
    }
}

Швидкий 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")

Я ще не впевнений, як це зробити у Свіфті.


1
Це, безумовно, працює, і, здається, компілятор досить агресивний у виведенні для вас типів NSString. Я дуже сподівався на чистий Швидкий спосіб зробити це, оскільки це здається досить поширеним випадком використання.
Метт Уайлдінг

Так, я оглядаюсь, але не бачу. Можливо, вони зосередилися на областях, не підтримуваних ObjC, оскільки вони можуть заповнити ці прогалини без надто багато втрачених можливостей. Тільки думаючи вголос :)
Логан

4

Якщо ви хочете знати позицію у вигляді символу в рядку в якості ІНТ використовувати значення цього:

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

Swift 5: value of type 'Range<String.Index>?' has no member 'location'.
Неф

3

Я знаю, що це давнє і відповідь прийнято, але ви можете знайти індекс рядка в декількох рядках коду, використовуючи:

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

Ще деякі чудові відомості про Swift струни тут Strings in Swift


findнедоступний у Swift 3
AamirR

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

1
Він працює лише тому, що "рядок" - це NSString, а не струна Свіфта
gderaco

2

Змінна тип String у Swift містить різні функції порівняно з NSString у Objective-C. І як згадував Султан,

Swift String не реалізує RandomAccessIndex

Що ви можете зробити, це занизити змінну типу String до NSString (це справедливо в Swift). Це дасть вам доступ до функцій в NSString.

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

2

Якщо ви думаєте про це, вам насправді не потрібна точна інта-версія розташування. Діапазону або навіть 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 як NSString) .rangeOfString (target) .location}
YannSteph

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

до вашого швидкого файлу та використовуйте всі "старі" методи.


1

З точки зору мислення це можна назвати НАПРЯМОЮ. Ви дізнаєтесь, що світ круглий, а не плоский. "Вам не потрібно знати INDEX персонажа, щоб робити з ним справи." І як програміст на 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

Якщо ви шукаєте простий спосіб отримати індекс характеру або рядка, який оформить замовлення в цій бібліотеці http://www.dollarswift.org/#indexof-char-character-int

Ви можете отримати indexOf з рядка, використовуючи також інший рядок або шаблон регулярного вираження


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)

Що це досягає? Наскільки ваш перший блок коду кращий за другий?
Бен Леджієро

0

Якщо вам потрібен лише індекс символу, найпростішим, швидким рішенням (як уже вказував Паскаль) є:

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

символи зараз знецінюються ... на жаль, навіть якщо це працює
user3069232

0

Що стосується перетворення String.Indexна 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.