Поиск индекса персонажа в Swift String
Пришло время признать свое поражение...
В Objective-C я мог бы использовать что-то вроде:
NSString* str = @"abcdefghi";
[str rangeOfString:@"c"].location; // 2
В Swift я вижу нечто подобное:
var str = "abcdefghi"
str.rangeOfString("c").startIndex
... но это только дает мне String.Index
, который я могу использовать, чтобы вставить обратно в исходную строку, но не извлечь местоположение из.
FWIW, это String.Index
имеет частный ивар под названием _position
это имеет правильное значение в нем. Я просто не вижу, как это выставлено.
Я знаю, что мог бы легко добавить это в String сам. Мне больше интересно, что мне не хватает в этом новом API.
33 ответа
Вы не единственный, кто не может найти решение.
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
это громоздко, но использование оболочки для индексации целыми числами (см. /questions/29974986/poisk-indeksa-personazha-v-swift-string/29975002#29975002) опасно, поскольку скрывает неэффективность реальной индексации.
Обратите внимание, что реализация индексации 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]
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
}
}
Swift 4.0
func indexInt(of char: Character) -> Int? {
return index(of: char)?.encodedOffset
}
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
}
}
Я нашел это решение для swift2:
var str = "abcdefghi"
let indexForCharacterInString = str.characters.indexOf("c") //returns 2
Вот чистое расширение 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
}
}
Я не уверен, как извлечь позицию из String.Index, но если вы хотите использовать некоторые инфраструктуры Objective-C, вы можете перейти к target-c и сделать это так же, как раньше.
"abcdefghi".bridgeToObjectiveC().rangeOfString("c").location
Похоже, что некоторые методы NSString еще не были (или, возможно, не будут) перенесены в String. Содержит и приходит в голову.
Вы также можете найти индексы символа в одной строке, как это,
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") // []
Swift 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")
}
Если вы хотите использовать знакомую NSString, вы можете объявить ее явно:
var someString: NSString = "abcdefghi"
var someRange: NSRange = someString.rangeOfString("c")
Я еще не уверен, как это сделать в Swift.
Если вы хотите узнать положение символа в строке как значение типа int, используйте это:
let loc = newString.range(of: ".").location
Я знаю, что это старый и ответ принят, но вы можете найти индекс строки в нескольких строках кода, используя:
var str : String = "abcdefghi"
let characterToFind: Character = "c"
let characterIndex = find(str, characterToFind) //returns 2
Некоторая другая отличная информация о строках Swift здесь Strings in Swift
Это сработало для меня,
var loc = "abcdefghi".rangeOfString("c").location
NSLog("%d", loc);
это тоже сработало,
var myRange: NSRange = "abcdefghi".rangeOfString("c")
var loc = myRange.location
NSLog("%d", loc);
Тип переменной String в Swift содержит различные функции по сравнению с NSString в Objective-C . И, как упомянул Султан,
Swift String не реализует RandomAccessIndex
То, что вы можете сделать, это уменьшить значение переменной типа String до NSString (это действительно в Swift). Это даст вам доступ к функциям в NSString.
var str = "abcdefghi" as NSString
str.rangeOfString("c").locationx // returns 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)
Если вы думаете об этом, вам на самом деле не нужна точная 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)
}
Swift 4 Полное решение:
OffsetIndexableCollection (строка, использующая Int Index)
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
}
String - это тип моста для NSString, поэтому добавьте
import Cocoa
в свой файл swift и используйте все "старые" методы.
String Extension {
//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
}
}
}
Вы можете найти порядковый номер символа в строке следующим образом:
var str = "abcdefghi"
if let index = str.firstIndex(of: "c") {
let distance = str.distance(from: str.startIndex, to: index)
// distance is 2
}
С точки зрения мышления это можно назвать ИНВЕРСИЕЙ. Вы обнаружите, что мир круглый, а не плоский. "Вам не нужно знать ИНДЕКС персонажа, чтобы что-то с ним делать". И как программист на Си, мне было трудно это воспринимать! Ваша строка "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!)
На предмет превращения 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, это не решение для вас.
Если вам нужен только индекс символа, самое простое и быстрое решение (как уже указывалось Паскалем):
let index = string.characters.index(of: ".")
let intIndex = string.distance(from: string.startIndex, to: index)
Я играю со следующими
extension String {
func allCharactes() -> [Character] {
var result: [Character] = []
for c in self.characters {
result.append(c)
}
return
}
}
пока я не пойму предоставленное, теперь это просто символьный массив
и с
let c = Array(str.characters)
Чтобы получить индекс подстроки в строке с помощью Swift 2:
let text = "abc"
if let range = text.rangeOfString("b") {
var index: Int = text.startIndex.distanceTo(range.startIndex)
...
}
Если вы ищете простой способ получить индекс проверки символов или строк, воспользуйтесь этой библиотекой http://www.dollarswift.org/.
Вы можете получить indexOf из строки, используя также другую строку или шаблон регулярных выражений
В 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")
}
На мой взгляд, лучший способ узнать саму логику - ниже.
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)
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");
}
В 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 ""
}