Поиск индекса персонажа в Swift String

160

Пришло время признать поражение...

В 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.

  • 0
    Вот проект GitHub, который содержит множество методов расширения для работы со строками Swift: github.com/iamjono/SwiftString
  • 0
    Лучшая реализация, которую я нашел, здесь: stackoverflow.com/a/32306142/4550651
Показать ещё 1 комментарий
Теги:
string

26 ответов

223
Лучший ответ

Вы не единственный, кто не смог найти решение.

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

Это означает, что любой полученный вами индекс должен быть создан из String.startIndex или String.endIndex (String.Index реализует BidirectionalIndexType). Любые другие индексы могут быть созданы с использованием методов successor или predecessor.

Теперь, чтобы помочь нам с индексами, существует набор методов (функций в Swift 1.x):

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)

Swift 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

Swift 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/questions/24029163/finding-index-of-character-in-swift-string) опасно, поскольку оно скрывает неэффективность реального индексации.

Обратите внимание, что при реализации индексации Swift существует проблема, заключающаяся в том, что индексы/диапазоны, созданные для одной строки, не могут быть надежно использованы для другой строки, например:

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]

Swift 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
    Неловко думал, что это может быть, это, кажется, ответ. Надеемся, что эти две функции диапазона войдут в документацию за некоторое время до финального релиза.
  • 0
    Какой тип является range в var range = text.rangeOfString("b")
Показать ещё 9 комментариев
80

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:

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:

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
    }
}
  • 0
    персонажи устарели !!
22
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 для массива. Люди хотели бы написать ужасно неэффективный код.
15

Я нашел это решение для swift2:

var str = "abcdefghi"
let indexForCharacterInString = str.characters.indexOf("c") //returns 2
  • 0
    что будет индексом, когда str = "abcdefgchi"
7

Вот чистое расширение строки, которое отвечает на вопрос:

Swift 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

Я не уверен, как извлечь позицию из String.Index, но если вы захотите вернуться к некоторым фреймворкам Objective-C, вы можете перейти на Objective-C и сделать это так же, как вы использовали,

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

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

  • 0
    На самом деле кажется, что доступ к свойству location возвращаемого значения достаточен для того, чтобы компилятор мог определить тип NSString, поэтому вызов bridgeToObjectiveC() не требуется. Моя проблема, кажется, проявляется только при вызове rangeOfString для ранее существующей строки Swift. Похоже, проблема с API ...
  • 0
    Это интересно. Я не знал этого в тех случаях. Хорошо, когда это уже строка, вы всегда можете использовать мост.
4

Если вы хотите использовать знакомый NSString, вы можете объявить его явно:

var someString: NSString = "abcdefghi"

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

Я еще не уверен, как это сделать в Swift.

  • 1
    Это, безусловно, работает, и кажется, что компилятор довольно агрессивно выводит типы NSString для вас. Я действительно надеялся на чистый способ Swift сделать это, так как это кажется достаточно распространенным вариантом использования.
  • 0
    Да, я смотрю вокруг, но я этого не вижу. Возможно, они сосредоточились на областях, не поддерживаемых ObjC, потому что они могут заполнить эти пробелы без слишком большого количества потерянных возможностей. Просто мысли вслух :)
3

Я знаю, что это старый и ответ принят, но вы можете найти индекс строки в пару строк кода, используя:

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

Некоторые другие замечательные сведения о строках Swift здесь Строки в Swift

  • 0
    Это работает только для символов, а не для строк.
  • 0
    find не доступен в Swift 3
2

Если вы хотите знать позицию символа в строке в качестве значения int, используйте это:

let loc = newString.range(of: ".").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)
2

Если вы думаете об этом, вам действительно не нужна точная версия Int. Диапазон или даже 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)
}
  • 0
    Лучший ответ для меня заключается в том, что func indexOf (target: String) -> Int {return (self as NSString) .rangeOfString (target) .location}
2

Это сработало для меня,

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

это тоже сработало,

var myRange: NSRange = "abcdefghi".rangeOfString("c")
var loc = myRange.location
NSLog("%d", loc);
  • 0
    Кажется, что оба они каким-то образом отступают от поведения NSString. На своей игровой площадке я использовал промежуточную переменную для строки. var str = "abcdef"; str.rangeOfString("c").location выдает ошибку о том, что String.Index не имеет члена с именем location ...
  • 1
    Это работает только потому, что "строка" - это NSString, а не строка Swift
1

С точки зрения мышления это можно назвать ИНВЕРСИЕЙ. Вы обнаруживаете, что мир круглый, а не плоский. "Вам действительно не нужно знать ИНДЕКС персонажа, чтобы что-то делать с ним". И как программист на C я обнаружил, что тяжело взять тоже! Ваша строка "let index = 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

Тип переменной String в Swift содержит разные функции по сравнению с NSString в Objective-C. И как сказал Султан,

Swift String не реализует RandomAccessIndex

Что вы можете сделать, это опустить вашу переменную типа String в NSString (это действительно в Swift). Это даст вам доступ к функциям в NSString.

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

String - это тип моста для NSString, поэтому добавьте

import Cocoa

в ваш быстрый файл и используйте все "старые" методы.

0

Swift 4 Complete Solution:

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

Swift 4.0

func indexInt(of char: Character) -> Int? {
    return index(of: char)?.encodedOffset        
}
  • 0
    возвращаемый индекс (из: элемента) .map {target.distance (от: startIndex до: $ 0)}
0

Swift 3

extension String {
        func substring(from:String) -> String
        {
            let searchingString = from
            let rangeOfSearchingString = self.range(of: searchingString)!
            let indexOfSearchingString: Int = self.distance(from: self.startIndex, to: rangeOfSearchingString.upperBound )
            let trimmedString = self.substring(start: indexOfSearchingString , end: self.count)

            return trimmedString
        }

    }
0

Что касается поворота a 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

Если вам нужен только индекс символа, самое простое, быстрое решение (как уже указывалось Паскалем):

let index = string.characters.index(of: ".")
let intIndex = string.distance(from: string.startIndex, to: index)
  • 0
    персонажи сейчас обесцениваются ... к сожалению, даже если это работает
0

Я играю со следующими

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

до тех пор, пока я не пойму, что он предоставлен, теперь просто массив символов

и

let c = Array(str.characters)
  • 0
    Что это делает? Как ваш первый блок кода лучше, чем второй?
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

В 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

В swift 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

Чтобы получить индекс подстроки в строке с Swift 2:

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

Если вы ищете простой способ получить индекс символьной или строковой проверки этой библиотеки http://www.dollarswift.org/#indexof-char-character-int

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню