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_position正しい値を持つプライベートivarが呼び出されます。それがどのように公開されているのかわかりません。

これを自分でStringに簡単に追加できることはわかっています。この新しいAPIで何が欠けているのか、もっと知りたいです。


Swiftの文字列操作のための多くの拡張メソッドを含むGitHubプロジェクトは次のとおり
RenniePet

私が見つけた最良の実装はここにある:stackoverflow.com/a/32306142/4550651
カルロス・ガルシア

Unicodeコードポイントと拡張書記素クラスタを区別する必要がありますか?
Ben Leggiero 2017

回答:


248

解決策を見つけられなかったのはあなただけではありません。

String実装していませんRandomAccessIndexType。おそらく、異なるバイト長の文字を使用できるためです。これが、文字数を取得するためにstring.characters.countcountまたはcountElementsSwift 1.xで)使用する必要がある理由です。それはポジションにも適用されます。これ_positionはおそらくバイトの生の配列へのインデックスであり、彼らはそれを公開したくありません。これString.Indexは、文字の途中のバイトにアクセスできないようにするためのものです。

つまり、取得するインデックスは、String.startIndexまたはString.endIndexString.Indeximplements BidirectionalIndexType)から作成する必要があります。その他のインデックスは、successorまたはpredecessorメソッドを使用して作成できます。

インデックスを支援するために、一連のメソッド(Swift 1.xの関数)があります。

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

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/a/25152652/669586を参照)、実際のインデックス作成の非効率性が隠されるため危険です。

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
ぎこちないかもしれないと思った、これが答えのようです。うまくいけば、これらの2つの範囲関数は、最終リリースの前のいずれかの時点でドキュメンテーションになります。
Matt Wilding

どのようなタイプであるrangevar range = text.rangeOfString("b")
zaph

5
@Zaph Everyに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
Foundation Stringとまったく同じようにNSString、@ deviosにはと呼ばれるメソッドがあり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
    }
}

スウィフト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を作成することを想像してください。人々はひどく非効率的なコードを書きたいと思っています。
Erik Engheim 2014年

16

私はswift2のこの解決策を見つけました:

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

str = "abcdefgchi"の場合のインデックスとなるもの
Vatsal Shukla、

10

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

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

それは面白い。そのような場合に推測されることを知りませんでした。まあ、それがすでに文字列の場合は、いつでもブリッジを使用できます。
Connor

8

これは、質問に答えるクリーンな文字列拡張です:

スウィフト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

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

ここスウィフト文字列に関するいくつかの他の偉大な情報スウィフトの文字列


findSwift 3では利用できません
AamirR 2017

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
「文字列」がSwiftの文字列ではなくNSStringであるという理由だけで機能します
gderaco

2

Swiftの変数型Stringには、Objective-CのNSStringとは異なる関数が含まれています。そして、スルタンが述べたように、

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

2

最も簡単な方法は次のとおりです。

スウィフト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

考えてみると、これは反転と呼ばれるかもしれません。世界が平らではなく丸いことに気づくでしょう。「キャラクターのインデックスを知っている必要はありません。そして、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

ただし、インデックスが必要な場合は、Int値では実際のINDEXを返す必要があります。これは、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 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
}

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

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

これは何を達成しますか?最初のコードブロックは2番目のコードブロックよりもどのように優れていますか?
Ben Leggiero 2017

0

文字のインデックスだけが必要な場合、(Pascalによってすでに指摘されているように)最も単純で迅速な解決策は次のとおりです。

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

文字が減価されました...残念ながら、これが機能しても
user3069232

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

スウィフト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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.