Swiftプログラミング言語で文字列のn番目の文字を取得する


419

文字列のn番目の文字を取得するにはどうすればよいですか?[]運が悪かったので、bracket()アクセサーを試しました。

var string = "Hello, world!"

var firstChar = string[0] // Throws error

エラー: 'subscript'は使用できません:Intで文字列に添字を付けることはできません。説明についてはドキュメントのコメントを参照してください


1
エラーメッセージ「Intで文字列に添字を付ける
andrewdotn

var firstChar = string.index(string.startIndex, offsetBy: 0)代わりに使用してください
Sazzad Hissain Khan

@SazzadHissainKhanこれは、文字ではなく文字列インデックスになります。ところで、なぜ単純ではないのですstring.startIndexか?最初の文字string[string.startIndex] または単にstring.first。文字列が空かどうかを最初に確認する必要がある最初のアプローチ、2番目のアプローチはオプションを返すことに注意してください
Leo Dabus

回答:


567

注意: Swift 4およびSwift 5の適切な実装については、Leo Dabusの回答を参照してください。

Swift 4以降

Substring型はSwift 4で導入され、元の文字列とストレージを共有することで部分文字列をより高速かつ効率的にするため、それが添え字関数によって返されます。

ここで試してみてください

extension StringProtocol {
    subscript(offset: Int) -> Character { self[index(startIndex, offsetBy: offset)] }
    subscript(range: Range<Int>) -> SubSequence {
        let startIndex = index(self.startIndex, offsetBy: range.lowerBound)
        return self[startIndex..<index(startIndex, offsetBy: range.count)]
    }
    subscript(range: ClosedRange<Int>) -> SubSequence {
        let startIndex = index(self.startIndex, offsetBy: range.lowerBound)
        return self[startIndex..<index(startIndex, offsetBy: range.count)]
    }
    subscript(range: PartialRangeFrom<Int>) -> SubSequence { self[index(startIndex, offsetBy: range.lowerBound)...] }
    subscript(range: PartialRangeThrough<Int>) -> SubSequence { self[...index(startIndex, offsetBy: range.upperBound)] }
    subscript(range: PartialRangeUpTo<Int>) -> SubSequence { self[..<index(startIndex, offsetBy: range.upperBound)] }
}

をに変換するSubstringにはString、単純にを実行できますがString(string[0..2])、部分文字列を保持する予定がある場合にのみ行う必要があります。それ以外の場合は、を保持する方が効率的Substringです。

誰かがこれらの2つの拡張機能を1つにマージするための良い方法を見つけられたら素晴らしいです。メソッドが存在しないStringProtocol ため、拡張に失敗indexしました。注:この回答は既に編集されており、適切に実装されており、部分文字列でも同様に機能します。StringProtocolタイプに添え字を付けるときにクラッシュしないように、有効な範囲を使用してください。範囲外の値でクラッシュしない範囲の添え字の場合、この実装を使用できます


なぜこれが組み込まれていないのですか?

エラーメッセージには、「説明についてはドキュメントのコメントを参照してください」と表示されます。AppleはUnavailableStringAPIs.swiftファイルで次の説明を提供しています。

整数の添え字付き文字列は使用できません。

i文字列のth番目の文字」の概念は、ライブラリやシステムコンポーネントによって解釈が異なります。ユースケースと関連するAPIに従って正しい解釈を選択する必要があるため、String 、整数を添え字にすることはできません。

Swiftは、文字列内に格納された文字データにアクセスするためのいくつかの異なる方法を提供します。

  • String.utf8文字列内のUTF-8コード単位のコレクションです。文字列をUTF-8に変換するときにこのAPIを使用します。ほとんどのPOSIX APIは、文字列をUTF-8コード単位で処理します。

  • String.utf16文字列のUTF-16コード単位のコレクションです。ほとんどのCocoaおよびCocoaタッチAPIは、UTF-16コード単位で文字列を処理します。たとえば、NSRange使用されているのインスタンスは 、UTF-16コード単位でのサブストリングのオフセットと長さNSAttributedStringNSRegularExpression格納します。

  • String.unicodeScalarsUnicodeスカラーのコレクションです。このAPIは、文字データを低レベルで操作するときに使用します。

  • String.characters 拡張された書記素クラスターのコレクションです。これは、ユーザーが認識する文字の近似です。

人間が読めるテキストを含む文字列を処理する場合、文字単位の処理は可能な限り回避する必要があることに注意してください。使用ハイレベルのロケールに依存するUnicodeのアルゴリズムに代えて、例えば、 String.localizedStandardCompare()String.localizedLowercaseStringString.localizedStandardRangeOfString()


4
Cannot find an overload for 'advance' that accepts an argument list of type '(String.Index, T)'... String.IndexInt互換性がありません。

7
あなたは、表示された場合Cannot subscript a value of type 'String'...:この回答をチェックstackoverflow.com/a/31265316/649379
SoftDesigner

24
これを使用しようとすると、が表示されますAmbiguous use of 'subscript'
ジョウイ2016年

18
警告!以下の拡張はひどく非効率的です。文字列が整数でアクセスされるたびに、その開始インデックスを進めるO(n)関数が実行されます。別の線形ループ内で線形ループを実行すると、このforループが誤ってO(n2)になります。つまり、文字列の長さが増えると、このループにかかる時間は2次的に増加します。そうする代わりに、キャラクターの文字列コレクションを使用できます。
ignaciohugog

2
fatal error: Can't form a Character from an empty String
Devin B

341

Swift 5.2

let str = "abcdef"
str[1 ..< 3] // returns "bc"
str[5] // returns "f"
str[80] // returns ""
str.substring(fromIndex: 3) // returns "def"
str.substring(toIndex: str.length - 2) // returns "abcd"

この文字列拡張をプロジェクトに追加する必要があります(完全にテストされています)。

extension String {

    var length: Int {
        return count
    }

    subscript (i: Int) -> String {
        return self[i ..< i + 1]
    }

    func substring(fromIndex: Int) -> String {
        return self[min(fromIndex, length) ..< length]
    }

    func substring(toIndex: Int) -> String {
        return self[0 ..< max(0, toIndex)]
    }

    subscript (r: Range<Int>) -> String {
        let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)),
                                            upper: min(length, max(0, r.upperBound))))
        let start = index(startIndex, offsetBy: range.lowerBound)
        let end = index(start, offsetBy: range.upperBound - range.lowerBound)
        return String(self[start ..< end])
    }
}

Swiftには常にこの問題に対する標準の解決策がありましたが(以下で提供するString拡張なし)、拡張機能を使用することを強くお勧めします。どうして?Stringの構文がほとんどすべてのリリースで変更されていたSwiftの初期バージョンから数十時間の面倒な移行を節約できたので、プロジェクト全体をリファクタリングするのではなく、拡張機能の実装を更新するだけで済みました。あなたの選択をしてください。

let str = "Hello, world!"
let index = str.index(str.startIndex, offsetBy: 4)
str[index] // returns Character 'o'

let endIndex = str.index(str.endIndex, offsetBy:-2)
str[index ..< endIndex] // returns String "o, worl"

String(str.suffix(from: index)) // returns String "o, world!"
String(str.prefix(upTo: index)) // returns String "Hell"

変更range.upperBound - range.lowerBoundrange.count
レオDabus

それは元の質問の一部ではありませんが、これもサポートされている割り当てであるとよいでしょう。例:s [i] = "a" :)。
Chris Prince、

2
Swift 4.2では、下付き文字はもう利用できないと思います。「下付き文字」は使用できません
。Intで

2
@ChrisPrinceextension StringProtocol where Self: RangeReplaceableCollection { subscript(offset: Int) -> Element { get { return self[index(startIndex, offsetBy: offset)] } set { let start = index(startIndex, offsetBy: offset) replaceSubrange(start..<index(after: start), with: [newValue]) } } }
Leo

これは組み込み関数でなければなりません
Ammar Mujeeb

150

私はこのきちんとした回避策を思いついたばかりです

var firstChar = Array(string)[0]

2
これは、UTF8またはASCIIでエンコードされた文字列があることがわかっている(一般的な)場合に適した迅速な回避策です。文字列が複数のバイトを使用するエンコーディングになっていないことを確認してください。
Jeff Hay

48
最初の文字を取得するためだけに文字列全体をコピーしているため、これは非常に非効率的です。string [string。スルタンが指摘したように0の代わりにstartIndex]。
ビョルン2015年

6
文字列のアンラップ: var firstChar = Array(string!)[0]それ以外の場合は言うでしょうadd arrayLiteral
Mohammad Zaid Pathan

2
私はこれが実際のところラウンドであるので、きれいだとは思いません。Arrayのどのイニシャライザが最初に使用されてこれが発生するのかはよくわかりません(これは、SequenceTypeイニシャライザが、配列の個々のコンポーネントとして文字列の文字を収集する原因となっていると想定しています)。これはまったく明示的ではなく、型キャストなしで将来修正される可能性があります。[string] .firstを介して配列の省略表現を使用する場合も、これは機能しません。@Sulthanのソリューションは、ベイクインされたインデックス値を使用するのに最適です。ここで何が起こっているかははるかに明確です。
TheCodingArt

2
うわー、コンパイラセグメントエラー!
フランク

124

整数のみを使用した索引付けはせず、のみを使用しString.Indexます。主に線形の複雑さ。から範囲を作成しString.Index、それらを使用して部分文字列を取得することもできます。

Swift 3.0

let firstChar = someString[someString.startIndex]
let lastChar = someString[someString.index(before: someString.endIndex)]
let charAtIndex = someString[someString.index(someString.startIndex, offsetBy: 10)]

let range = someString.startIndex..<someString.index(someString.startIndex, offsetBy: 10)
let substring = someString[range]

Swift 2.x

let firstChar = someString[someString.startIndex]
let lastChar = someString[someString.endIndex.predecessor()]
let charAtIndex = someString[someString.startIndex.advanceBy(10)]

let range = someString.startIndex..<someString.startIndex.advanceBy(10)
let subtring = someString[range]

ある文字列から別の文字列に作成されたインデックス(または範囲)を使用することはできません。

let index10 = someString.startIndex.advanceBy(10)

//will compile
//sometimes it will work but sometimes it will crash or result in undefined behaviour
let charFromAnotherString = anotherString[index10]

7
Stringインデックスは文字列に固有です。これは、文字列が異なるとマルチユニットUTF-16が異なる場合Charactersや、位置が異なる場合があるため、UTF-16ユニットインデックスが一致しない場合や、マルチユニットUTF-16内の端またはポイントを超える場合があるためCharacterです。
zaph 2014

@Zaphそれは明らかです。
スルタン2014

3
あなたが言う理由の説明:「時々それはクラッシュするか、未定義の振る舞いをもたらすでしょう」。たぶんそれをしないでくださいと言ったほうがいいかもしれません...
zaph

1
@Sulthan ....<(への割り当てでrange)います
Aaron Brager 2014年

3
@CajunLukeこのコメントを投稿してからしばらく経っていますが、この回答をご覧ください。使用できますvar lastChar = string[string.endIndex.predecessor()]
David L

122

Xcode 11•Swift 5.1

StringProtocolを拡張して、サブストリングでもサブスクリプトを使用できるようにすることができます。

extension StringProtocol {
    subscript(_ offset: Int)                     -> Element     { self[index(startIndex, offsetBy: offset)] }
    subscript(_ range: Range<Int>)               -> SubSequence { prefix(range.lowerBound+range.count).suffix(range.count) }
    subscript(_ range: ClosedRange<Int>)         -> SubSequence { prefix(range.lowerBound+range.count).suffix(range.count) }
    subscript(_ range: PartialRangeThrough<Int>) -> SubSequence { prefix(range.upperBound.advanced(by: 1)) }
    subscript(_ range: PartialRangeUpTo<Int>)    -> SubSequence { prefix(range.upperBound) }
    subscript(_ range: PartialRangeFrom<Int>)    -> SubSequence { suffix(Swift.max(0, count-range.lowerBound)) }
}

extension LosslessStringConvertible {
    var string: String { .init(self) }
}

extension BidirectionalCollection {
    subscript(safe offset: Int) -> Element? {
        guard !isEmpty, let i = index(startIndex, offsetBy: offset, limitedBy: index(before: endIndex)) else { return nil }
        return self[i]
    }
}

テスト中

let test = "Hello USA 🇺🇸!!! Hello Brazil 🇧🇷!!!"
test[safe: 10]   // "🇺🇸"
test[11]   // "!"
test[10...]   // "🇺🇸!!! Hello Brazil 🇧🇷!!!"
test[10..<12]   // "🇺🇸!"
test[10...12]   // "🇺🇸!!"
test[...10]   // "Hello USA 🇺🇸"
test[..<10]   // "Hello USA "
test.first   // "H"
test.last    // "!"

// Subscripting the Substring
 test[...][...3]  // "Hell"

// Note that they all return a Substring of the original String.
// To create a new String from a substring
test[10...].string  // "🇺🇸!!! Hello Brazil 🇧🇷!!!"

「self [index(startIndex、offsetBy:i)]」とは何ですか?そして、「self [i]」はどのように機能しますか?
アレンリンリ

1
こんにちはレオ、解決策をありがとう!私は(今日)Swift 2.3から3に切り替えただけで、ソリューションの添え字(範囲:Range <Int>)により、「Extra argument 'limitedBy' in call」というエラーが表示されます。何が間違っていると思いますか?
AhmetAkkök2016年

@AhmetAkkök本当にコードを変更しなかったのですか?
Leo Dabus 2016年

1
@Leoそれは私がプロジェクト全体を変換しなかったことが判明しましたが、拡張ではなくアプリで、私はアプリと拡張の両方に対してプロセスを繰り返しました、そしてそれは今や問題なく動作します。あなたの助けは大歓迎です!
AhmetAkkök16年

これは非常に複雑なコードです。return String(Array(characters)[range])Swift 3で行うよりも優れている点は何ですか?
Dan Rosenstark、2016年

69

スウィフト4

let str = "My String"

インデックスの文字列

let index = str.index(str.startIndex, offsetBy: 3)
String(str[index])    // "S"

サブストリング

let startIndex = str.index(str.startIndex, offsetBy: 3)
let endIndex = str.index(str.startIndex, offsetBy: 7)
String(str[startIndex...endIndex])     // "Strin"

最初のn文字

let startIndex = str.index(str.startIndex, offsetBy: 3)
String(str[..<startIndex])    // "My "

最後のn文字

let startIndex = str.index(str.startIndex, offsetBy: 3)
String(str[startIndex...])    // "String"

Swift 2および3

str = "My String"

**インデックスの文字列**

スウィフト2

let charAtIndex = String(str[str.startIndex.advancedBy(3)])  // charAtIndex = "S"

スウィフト3

str[str.index(str.startIndex, offsetBy: 3)]

SubString fromIndex toIndex

スウィフト2

let subStr = str[str.startIndex.advancedBy(3)...str.startIndex.advancedBy(7)] // subStr = "Strin"

スウィフト3

str[str.index(str.startIndex, offsetBy: 3)...str.index(str.startIndex, offsetBy: 7)]

最初のn文字

let first2Chars = String(str.characters.prefix(2)) // first2Chars = "My"

最後のn文字

let last3Chars = String(str.characters.suffix(3)) // last3Chars = "ing"

24

Xcode 7 GMシード時点のSwift 2.0

var text = "Hello, world!"

let firstChar = text[text.startIndex.advancedBy(0)] // "H"

n番目の文字については、0をn-1に置き換えます。

編集:Swift 3.0

text[text.index(text.startIndex, offsetBy: 0)]


nb文字列内の特定の文字を取得する簡単な方法があります

例えば let firstChar = text.characters.first


23

Cannot subscript a value of type 'String'...この拡張機能を使用している場合:

スウィフト3

extension String {
    subscript (i: Int) -> Character {
        return self[self.characters.index(self.startIndex, offsetBy: i)]
    }

    subscript (i: Int) -> String {
        return String(self[i] as Character)
    }

    subscript (r: Range<Int>) -> String {
        let start = index(startIndex, offsetBy: r.lowerBound)
        let end = index(startIndex, offsetBy: r.upperBound)
        return self[start..<end]
    }

    subscript (r: ClosedRange<Int>) -> String {
        let start = index(startIndex, offsetBy: r.lowerBound)
        let end = index(startIndex, offsetBy: r.upperBound)
        return self[start...end]
    }
}

Swift 2.3

extension String {
    subscript(integerIndex: Int) -> Character {
        let index = advance(startIndex, integerIndex)
        return self[index]
    }

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

出典:http : //oleb.net/blog/2014/07/swift-strings/


19

Swift 2.2ソリューション:

次の拡張機能はXcode 7で機能します。これは、このソリューションとSwift 2.0構文変換の組み合わせです。

extension String {
    subscript(integerIndex: Int) -> Character {
        let index = startIndex.advancedBy(integerIndex)
        return self[index]
    }

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

14

Swift文字列クラスは、UTF文字をネイティブでサポートしているため、特定のインデックスで文字を取得する機能を提供しません。メモリ内のUTF文字の可変長により、文字に直接ジャンプすることが不可能になります。つまり、毎回手動で文字列をループする必要があります。

Stringを拡張して、目的のインデックスになるまで文字をループするメソッドを提供できます

extension String {
    func characterAtIndex(index: Int) -> Character? {
        var cur = 0
        for char in self {
            if cur == index {
                return char
            }
            cur++
        }
        return nil
    }
}

myString.characterAtIndex(0)!

3
あなたはすでに文字列をループすることができます: "foo"の文字{println(letter)}
Doobeh

@Doobeh上の編集のように、ループスルーして実際のキャラクターを返すことを意味しました
drewag

いいね!それを反復する方法は面白いですが、インデックス経由ではできません。スウィフトの感覚はパイソンのようですが、エッジが硬くなっています。
Doobeh

を使用して見つかった、myString.bridgeToObjectiveC().characterAtIndex(0)または(string as NSString ).characterAtIndex(0) 文字のInt値を返す
markhunte

4
NSStringSwiftネイティブから個々の文字にアクセスするためのメソッドを使用しないでくださいString。2つは異なるカウントメカニズムを使用するため、より高いUnicode文字で予期しない結果が得られます。最初の方法は安全でなければなりません(SwiftのUnicodeバグが処理された後)。
Nate Cook

11

余談ですが、次のように、文字列の文字チェーン表現に直接適用できる関数がいくつかあります。

var string = "Hello, playground"
let firstCharacter = string.characters.first // returns "H"
let lastCharacter = string.characters.last // returns "d"

結果は文字型ですが、文字列にキャストできます。

またはこれ:

let reversedString = String(string.characters.reverse())
// returns "dnuorgyalp ,olleH" 

:-)


11

スウィフト4

String(Array(stringToIndex)[index]) 

これはおそらく、この問題を一度だけ解決する最良の方法です。最初に文字列を配列としてキャストし、次に結果を再び文字列としてキャストしたい場合があります。それ以外の場合、文字列の代わりに文字が返されます。

例でString(Array("HelloThere")[1])は、文字列として「e」を返します。

(Array("HelloThere")[1] 文字として「e」を返します。

Swiftは、配列のように文字列にインデックスを付けることを許可していませんが、これにより、ブルートフォーススタイルの仕事が完了します。


1
文字列の内容全体を別のメモリ位置に複製すると、特に大きな文字列の場合、パフォーマンスが低下します。ダイレクトメモリアクセスなどの単純なタスクでは、追加のメモリ割り当ては必要ありません。
Cristik

7

私の非常に簡単な解決策:

Swift 4.1:

let myString = "Test string"
let index = 0
let firstCharacter = myString[String.Index(encodedOffset: index)]

Swift 5.1:

let firstCharacter = myString[String.Index.init(utf16Offset: index, in: myString)]

Swift 4.1で動作
リーン


@Linh Dao encodingOffsetは使用しないでください。encodingOffsetは廃止されました:最も一般的な使用法が正しくないため、encodedOffsetは廃止されました。
レオダバス

@OhadMが最も単純であっても、それが正しい、または少なくとも期待どおりに機能しないというわけではありません。試してみてくださいlet flags = "🇺🇸🇧🇷" flags[String.Index(utf16Offset: 4, in: flags)] // "🇧🇷"
Leo Dabus

1
@OhadM私のコメントは単なる警告でした。期待どおりに動作すると思われる場合は、自由に使用してください。
レオダバス

6

あなたは文字列を配列に変換することでそれを行うことができ、以下のように添え字を使用して特定のインデックスでそれを取得することができます

var str = "Hello"
let s = Array(str)[2]
print(s)

1
このソリューションではコンテンツが重複し、メモリやCPUのパフォーマンスが低下することに注意してください。
クリスティク

5

私は同じ問題を抱えていました。単にこれを行う:

var aString: String = "test"
var aChar:unichar = (aString as NSString).characterAtIndex(0)

これは、実際にで「文字」を2回以上使用する多くの絵文字やその他の文字では失敗しますNSString
rmaddy

5

Swift3

添字構文を使用して、特定の文字列インデックスにある文字にアクセスできます。

let greeting = "Guten Tag!"
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index] // a

訪問 https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html

または、Swift 4で文字列拡張を行うことができます

extension String {
    func getCharAtIndex(_ index: Int) -> Character {
        return self[self.index(self.startIndex, offsetBy: index)]
    }
}

使用法:

let foo = "ABC123"
foo.getCharAtIndex(2) //C

3

私の解決策は1行です。cadenaが文字列で、4が必要なn番目の位置であるとします。

let character = cadena[advance(cadena.startIndex, 4)]

シンプル...将来のバージョンでは、Swiftにサブストリングに関するものがさらに含まれると思います。


1
それはvar charAtIndex = string[advance(string.startIndex, 10)]スルタンの答えと同じではありませんか?
マーティンR

はい、それはスルタンが言ったような別の例を持つ同じソリューションです。重複して申し訳ありません。:)二人が同じ方法を見つけるのは簡単です。
フリオセザールフェルナンデスムニョス2014

3

Swift 3:別のソリューション(遊び場でテスト済み)

extension String {
    func substr(_ start:Int, length:Int=0) -> String? {
        guard start > -1 else {
            return nil
        }

        let count = self.characters.count - 1

        guard start <= count else {
            return nil
        }

        let startOffset = max(0, start)
        let endOffset = length > 0 ? min(count, startOffset + length - 1) : count

        return self[self.index(self.startIndex, offsetBy: startOffset)...self.index(self.startIndex, offsetBy: endOffset)]
    }
}

使用法:

let txt = "12345"

txt.substr(-1) //nil
txt.substr(0) //"12345"
txt.substr(0, length: 0) //"12345"
txt.substr(1) //"2345"
txt.substr(2) //"345"
txt.substr(3) //"45"
txt.substr(4) //"5"
txt.substr(6) //nil
txt.substr(0, length: 1) //"1"
txt.substr(1, length: 1) //"2"
txt.substr(2, length: 1) //"3"
txt.substr(3, length: 1) //"4"
txt.substr(3, length: 2) //"45"
txt.substr(3, length: 3) //"45"
txt.substr(4, length: 1) //"5"
txt.substr(4, length: 2) //"5"
txt.substr(5, length: 1) //nil
txt.substr(5, length: -1) //nil
txt.substr(-1, length: -1) //nil

2

Swift 2.0 subStringの更新

public extension String {
    public subscript (i: Int) -> String {
        return self.substringWithRange(self.startIndex..<self.startIndex.advancedBy(i + 1))
    }

    public subscript (r: Range<Int>) -> String {
        get {
            return self.substringWithRange(self.startIndex.advancedBy(r.startIndex)..<self.startIndex.advancedBy(r.endIndex))
        }
    }

}

2

最初の文字を取得するための速い答えは次のようになると思います:

let firstCharacter = aString[aString.startIndex]

それはとてもエレガントで高性能です:

let firstCharacter = Array(aString.characters).first

しかし、文字列を操作してより多くの操作を実行したい場合は、拡張機能を作成すると考えることができます。

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

subscript(integerIndex: Int) -> Character {
    let index = startIndex.advancedBy(integerIndex)
    return self[index]
}

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

}

しかし、それはひどい考えです!!

以下の拡張はひどく非効率的です。文字列が整数でアクセスされるたびに、その開始インデックスを進めるO(n)関数が実行されます。別の線形ループ内で線形ループを実行すると、このforループが誤ってO(n2)になります。つまり、文字列の長さが増えると、このループにかかる時間は2次的に増加します。

そうする代わりに、キャラクターの文字列コレクションを使用できます。


2

スウィフト3

extension String {

    public func charAt(_ i: Int) -> Character {
        return self[self.characters.index(self.startIndex, offsetBy: i)]
    }

    public subscript (i: Int) -> String {
        return String(self.charAt(i) as Character)
    }

    public subscript (r: Range<Int>) -> String {
        return substring(with: self.characters.index(self.startIndex, offsetBy: r.lowerBound)..<self.characters.index(self.startIndex, offsetBy: r.upperBound))
    }

    public subscript (r: CountableClosedRange<Int>) -> String {
        return substring(with: self.characters.index(self.startIndex, offsetBy: r.lowerBound)..<self.characters.index(self.startIndex, offsetBy: r.upperBound))
    }

}

使用法

let str = "Hello World"
let sub = str[0...4]

役立つプログラミングのヒントとコツ(私が作成)


2

添え字の取得と設定(文字列と部分文字列)-Swift 4.2

Swift 4.2、Xcode 10

私は@alecarlsonの回答に基づいて回答を作成しました。唯一の大きな違いはSubstringまたはがString返されることです(場合によっては単一のCharacter)。あなたはすることもできますgetし、set下付き。 最後に、私のものは@alecarlsonの回答よりも少し面倒で長いため、ソースファイルに入れることをお勧めします。


拡張:

public extension String {
    public subscript (i: Int) -> Character {
        get {
            return self[index(startIndex, offsetBy: i)]
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
    public subscript (bounds: CountableRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ..< end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeThrough<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ... end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ..< end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }

    public subscript (i: Int) -> String {
        get {
            return "\(self[index(startIndex, offsetBy: i)])"
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            self.replaceSubrange(n...n, with: "\(c)")
        }
    }
    public subscript (bounds: CountableRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ..< end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeThrough<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ... end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ..< end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }

    public subscript (i: Int) -> Substring {
        get {
            return Substring("\(self[index(startIndex, offsetBy: i)])")
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
}
public extension Substring {
    public subscript (i: Int) -> Character {
        get {
            return self[index(startIndex, offsetBy: i)]
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }

    }
    public subscript (bounds: CountableRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ..< end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: PartialRangeThrough<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ... end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ..< end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }
    public subscript (i: Int) -> String {
        get {
            return "\(self[index(startIndex, offsetBy: i)])"
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
    public subscript (bounds: CountableRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ..< end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeThrough<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ... end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ..< end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }

    public subscript (i: Int) -> Substring {
        get {
            return Substring("\(self[index(startIndex, offsetBy: i)])")
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
}

これは、startIndexから両方のインデックス(開始と終了)を不必要にオフセットします。range.countを使用して終了インデックスをオフセットし、開始インデックスをオフセットすることができます
Leo Dabus

2

Swift 4.2

それが延びているので、この答えは理想的であるStringとそのすべてのSubsequencesSubstring1つの拡張子で)

public extension StringProtocol {

    public subscript (i: Int) -> Element {
        return self[index(startIndex, offsetBy: i)]
    }

    public subscript (bounds: CountableClosedRange<Int>) -> SubSequence {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[start...end]
    }

    public subscript (bounds: CountableRange<Int>) -> SubSequence {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[start..<end]
    }

    public subscript (bounds: PartialRangeUpTo<Int>) -> SubSequence {
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[startIndex..<end]
    }

    public subscript (bounds: PartialRangeThrough<Int>) -> SubSequence {
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[startIndex...end]
    }

    public subscript (bounds: CountablePartialRangeFrom<Int>) -> SubSequence {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        return self[start..<endIndex]
    }
}

使用法

var str = "Hello, playground"

print(str[5...][...5][0])
// Prints ","

これは、から両方のインデックス(startおよびend)を不必要にオフセットしますstartIndexendrange.countを使用してインデックスをオフセットし、インデックスをオフセットすることができstartます
Leo Dabus

2

現在、下付き文字(_ :)は使用できません。これができないだけでなく

str[0] 

"String.Index"を指定する必要がありますが、この方法で独自のインデックス番号を指定するには、代わりに次のように使用できます。

string[str.index(str.startIndex, offsetBy: 0)]

コードのみの回答を投稿するのではなく、回答を編集して、回答がどのように問題を解決するかを説明することで、コンテキストを追加してください。口コミから
Pedram Parsian

なぜ不必要なオフセットを行うのですか?なぜ単純ではないのですstring[string.startIndex]か?ところで、2つの異なる変数名を使用したため、コードは正しく動作/コンパイルしません。
クリスティク

2

Swift 4.2以降

Stringindicesプロパティを使用した範囲と部分的な範囲の添え字

@LeoDabusのいい答えのバリエーションとして、カスタムの添え字を(特殊な範囲と部分的な範囲によって)後​​者に実装するときDefaultIndicesindicesプロパティにフォールバックできるようにするために、に追加の拡張機能を追加できます。StringInt

extension DefaultIndices {
    subscript(at: Int) -> Elements.Index { index(startIndex, offsetBy: at) }
}

// Moving the index(_:offsetBy:) to an extension yields slightly
// briefer implementations for these String extensions.
extension String {
    subscript(range: Range<Int>) -> SubSequence {
        let start = indices[range.lowerBound]
        return self[start..<indices[start...][range.count]]
    }
    subscript(range: ClosedRange<Int>) -> SubSequence {
        let start = indices[range.lowerBound]
        return self[start...indices[start...][range.count]]
    }
    subscript(range: PartialRangeFrom<Int>) -> SubSequence {
        self[indices[range.lowerBound]...]
    }
    subscript(range: PartialRangeThrough<Int>) -> SubSequence {
        self[...indices[range.upperBound]]
    }
    subscript(range: PartialRangeUpTo<Int>) -> SubSequence {
        self[..<indices[range.upperBound]]
    }
}

let str = "foo bar baz bax"
print(str[4..<6]) // "ba"
print(str[4...6]) // "bar"
print(str[4...])  // "bar baz bax"
print(str[...6])  // "foo bar"
print(str[..<6])  // "foo ba"

下付き文字のindices代わりに(その他の)プロパティとしてプロパティを使用する方向に私を指摘してくれた@LeoDabusに感謝しStringます!


1
唯一の欠点は、CountableClosedRangeがstartIndexから両方のインデックスをオフセットすることです
Leo Dabus

1
@LeoDabusなるほど。はい、ほとんどLinuxですが、最近のSwiftはそれほど多くありません:/使用しているswiftenvときに使用しますが、4.2でもすぐに更新されると思います。
dfri

1
@LeoDabusこの回答を最新のSwiftに更新していただきありがとうございます。
dfri

1
@LeoDabusいい仕事です!後で詳細を調べる必要がありますが、順序付けられた/カウント可能なセットのタイプのいくつかをFoundationに頼らなければならなかったことを私は好きではなかったことを思い出します。
dfri

1
ありがとう!!!
レオダバス

2

Swift 5.1.3:

文字列拡張を追加します。

extension String {

 func stringAt(_ i: Int) -> String { 
   return String(Array(self)[i]) 
 } 

 func charAt(_ i: Int) -> Character { 
  return Array(self)[i] 
 } 
}

let str = "Teja Kumar"
let str1: String = str.stringAt(2)  //"j"
let str2: Character = str.charAt(5)  //"k"

1
これにより、このプロパティを呼び出してそこから単一の文字を抽出するたびに、文字列全体が文字の配列に変換されます。
レオダバス

1

スウィフトのStringタイプは提供しませんcharacterAtIndexUnicode文字列をエンコードするにはいくつかの方法があるメソッドを。あなたはUTF8、UTF16、または何か他のものに行きますか?

およびプロパティをCodeUnit取得することで、コレクションにアクセスできます。を取得してコレクションにアクセスすることもできますString.utf8String.utf16UnicodeScalarString.unicodeScalarsプロパティをます。

NSStringの実装の精神で、unichar型を返しています。

extension String
{
    func characterAtIndex(index:Int) -> unichar
    {
        return self.utf16[index]
    }

    // Allows us to use String[index] notation
    subscript(index:Int) -> unichar
    {
        return characterAtIndex(index)
    }
}

let text = "Hello Swift!"
let firstChar = text[0]

これは、16ビットより多くのストレージを必要とする文字では失敗します。基本的に、U + FFFFを超える任意のUnicode文字。
rmaddy

1

件名をフィードして迅速な添え字の可能性を示すために、次の文字列「substring-toolbox」添え字ベース

これらのメソッドは安全であり、文字列インデックスを決して超えません

extension String {
    // string[i] -> one string char
    subscript(pos: Int) -> String { return String(Array(self)[min(self.length-1,max(0,pos))]) }

    // string[pos,len] -> substring from pos for len chars on the left
    subscript(pos: Int, len: Int) -> String { return self[pos, len, .pos_len, .left2right] }

    // string[pos, len, .right2left] -> substring from pos for len chars on the right
    subscript(pos: Int, len: Int, way: Way) -> String { return self[pos, len, .pos_len, way] }

    // string[range] -> substring form start pos on the left to end pos on the right
    subscript(range: Range<Int>) -> String { return self[range.startIndex, range.endIndex, .start_end, .left2right] }

    // string[range, .right2left] -> substring start pos on the right to end pos on the left
    subscript(range: Range<Int>, way: Way) -> String { return self[range.startIndex, range.endIndex, .start_end, way] }

    var length: Int { return countElements(self) }
    enum Mode { case pos_len, start_end }
    enum Way { case left2right, right2left }
    subscript(var val1: Int, var val2: Int, mode: Mode, way: Way) -> String {
        if mode == .start_end {
            if val1 > val2 { let val=val1 ; val1=val2 ; val2=val }
            val2 = val2-val1
        }
        if way == .left2right {
            val1 = min(self.length-1, max(0,val1))
            val2 = min(self.length-val1, max(1,val2))
        } else {
            let val1_ = val1
            val1 = min(self.length-1, max(0, self.length-val1_-val2 ))
            val2 = max(1, (self.length-1-val1_)-(val1-1) )
        }
        return self.bridgeToObjectiveC().substringWithRange(NSMakeRange(val1, val2))

        //-- Alternative code without bridge --
        //var range: Range<Int> = pos...(pos+len-1)
        //var start = advance(startIndex, range.startIndex)
        //var end = advance(startIndex, range.endIndex)
        //return self.substringWithRange(Range(start: start, end: end))
    }
}


println("0123456789"[3]) // return "3"

println("0123456789"[3,2]) // return "34"

println("0123456789"[3,2,.right2left]) // return "56"

println("0123456789"[5,10,.pos_len,.left2right]) // return "56789"

println("0123456789"[8,120,.pos_len,.right2left]) // return "01"

println("0123456789"[120,120,.pos_len,.left2right]) // return "9"

println("0123456789"[0...4]) // return "01234"

println("0123456789"[0..4]) // return "0123"

println("0123456789"[0...4,.right2left]) // return "56789"

println("0123456789"[4...0,.right2left]) // return "678" << because ??? range can wear endIndex at 0 ???

1

負のインデックスを使用できるようにするpythonのようなソリューション、

var str = "Hello world!"
str[-1]        // "!"

になり得る:

extension String {
    subscript (var index:Int)->Character{
        get {
            let n = distance(self.startIndex, self.endIndex)
            index %= n
            if index < 0 { index += n }
            return self[advance(startIndex, index)]
        }
    }
}

ところで、Python全体のスライス表記を転置することは価値があるかもしれません


Swift 4用にコンパイルできるものを書いていただけませんか。最後のリターン...行が機能していないようです。advance()関数はありません。
C0D3

1

文字列をそのような文字の配列に変換することもできます:

let text = "My Text"
let index = 2
let charSequence = text.unicodeScalars.map{ Character($0) }
let char = charSequence[index]

これは、一定の時間内に指定されたインデックスでcharを取得する方法です。

以下の例は一定時間で実行されませんが、線形時間を必要とします。したがって、インデックスによる文字列の検索が多い場合は、上記の方法を使用してください。

let char = text[text.startIndex.advancedBy(index)]
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.