web-dev-qa-db-ja.com

Swiftにおける文字列部分文字列の仕組み

私はSwift 3で私の古いコードと答えのいくつかを更新してきました、しかし、私が部分文字列でSwift StringsとIndexingを得たとき、物事は混乱しました。 

具体的には、次のことを試していました。

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5)
let prefix = str.substringWithRange(prefixRange)

2行目で次のエラーが発生した

'String'型の値には 'substringWithRange'メンバがありません

Stringには現在以下のメソッドがあります。

str.substring(to: String.Index)
str.substring(from: String.Index)
str.substring(with: Range<String.Index>)

これらは最初は本当に私を混乱させていたので、 indexとrange で遊んでみました。これは部分文字列に対するフォローアップの質問と回答です。それらがどのように使用されているかを示すために、以下に回答を追加します。

270
Suragch

enter image description here

以下の例はすべて使用しています 

var str = "Hello, playground"

スイフト4

Swift 4では、文字列がかなり大きな見直しをされました。文字列から部分文字列を取得すると、SubstringではなくString型に戻ります。どうしてこれなの?文字列はSwiftの値型です。つまり、新しい文字列を作成するために1つの文字列を使用する場合は、それをコピーする必要があります。これは安定性にとっては良いことですが(あなたの知らないうちに他の誰かがそれを変更することはないでしょう)、効率性にとっては悪いことです。 

一方、Substringは、元のStringへの参照です。これは ドキュメンテーション からのイメージです。

コピーは必要ないので、使用するほうがはるかに効率的です。しかし、100万文字の文字列から10文字の部分文字列を取得したとします。サブストリングはストリングを参照しているので、サブストリングがある限り、システムはストリング全体を保持しなければなりません。したがって、サブストリングの操作が終わったら、それをストリングに変換してください。

let myString = String(mySubstring)

これは部分文字列だけをコピーし、古い文字列はガベージコレクションされます。サブストリング(タイプとして)は短命であることを意味しています。

Swift 4のもう1つの大きな改善は、文字列がコレクションであることです(これもまた)。つまり、Collectionに対してできることはすべて、Stringに対してもできるということです(添え字を使用する、文字を繰り返し処理する、フィルタをかけるなど)。 

次の例は、Swiftで部分文字列を取得する方法を示しています。

部分文字列を取得する

添え字または他のさまざまな方法(たとえば、prefixsuffixsplit)を使用して、文字列から部分文字列を取得できます。ただし、範囲にはIntインデックスではなくString.Indexを使用する必要があります。 ( 私の他の答え あなたがそれを手助けする必要があるなら/を見てください。)

文字列の始まり

下付き文字を使うことができます(Swift 4の片側範囲に注意してください)。

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello

またはprefix

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str.prefix(upTo: index) // Hello

あるいはもっと簡単です。

let mySubstring = str.prefix(5) // Hello

文字列の終わり

下付き文字を使う:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str[index...] // playground

またはsuffix

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground

あるいはもっと簡単です。

let mySubstring = str.suffix(10) // playground

suffix(from: index)を使うとき、私は-10を使うことによって終わりからカウントダウンしなければならなかったことに注意してください。 Stringの最後のx文字だけを受け取るsuffix(x)を使用する場合は、これは必要ありません。

文字列の範囲

繰り返しますが、ここでは単に添え字を使います。

let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end

let mySubstring = str[range]  // play

SubstringからStringへの変換

忘れないでください、あなたがあなたの部分文字列を保存する準備ができているとき、あなたは古い文字列のメモリがクリーンアップされることができるようにそれをStringに変換するべきです。

let myString = String(mySubstring)

Intインデックス拡張を使用しますか?

Airspeed VelocityとOle Begemannによる Strift in Swift 3 を読んだ後、Intベースのインデックス拡張を使うのはためらっています。 Swift 4では、Stringはコレクションですが、Swiftチームは意図的にIntインデックスを使用していません。まだString.Indexです。これは、Swift Charactersがさまざまな数のUnicodeコードポイントで構成されていることと関係があります。実際のインデックスはすべての文字列に対して一意に計算される必要があります。 

私は、Swiftチームが将来String.Indexを抽象化する方法を見つけられることを願います。しかしそれらが出るまで私は彼らのAPIを使うことを選んでいます。 String操作は単純なIntインデックスの検索ではないことを覚えておくと便利です。 

660
Suragch

私はSwiftのStringアクセスモデルに本当に不満を感じています。すべてがIndexである必要があります。私が欲しいのは、不器用なインデックスやアドバンス(メジャーリリースごとに変わる)ではなくIntを使って文字列のi番目の文字にアクセスすることです。そこで私はStringを拡張しました。

extension String {
    func index(from: Int) -> Index {
        return self.index(startIndex, offsetBy: from)
    }

    func substring(from: Int) -> String {
        let fromIndex = index(from: from)
        return substring(from: fromIndex)
    }

    func substring(to: Int) -> String {
        let toIndex = index(from: to)
        return substring(to: toIndex)
    }

    func substring(with r: Range<Int>) -> String {
        let startIndex = index(from: r.lowerBound)
        let endIndex = index(from: r.upperBound)
        return substring(with: startIndex..<endIndex)
    }
}

let str = "Hello, playground"
print(str.substring(from: 7))         // playground
print(str.substring(to: 5))           // Hello
print(str.substring(with: 7..<11))    // play
150
Code Different

スイフト4エクステンション:

extension String { 
    subscript(_ range: CountableRange<Int>) -> String { 
        let idx1 = index(startIndex, offsetBy: max(0, range.lowerBound))
        let idx2 = index(startIndex, offsetBy: min(self.count, range.upperBound))
        return String(self[idx1..<idx2])
    }    
}       

使用法: 

let s = "hello"
s[0..<3] // "hel"
s[3..<s.count] // "lo"

またはユニコード:

let s = "????????????"
s[0..<1] // "????"
52
Lou Zell

スイフト4

Swift 4では、StringCollectionに準拠しています。 substringではなく、subscript.を使用する必要があります。したがって、"play"からWord "Hello, playground"のみを切り取りたい場合は、次のようにします。

var str = "Hello, playground"
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let result = str[start..<end] // The result is of type Substring

知っておくと面白いのは、そうすることでSubstringではなくStringが得られるということです。 Substringはその記憶領域を元のStringと共有するので、これは速くて効率的です。ただし、この方法でメモリを共有すると、メモリリークが発生しやすくなります。

これが、元の文字列をクリーンアップしたい場合に、結果を新しい文字列にコピーする必要がある理由です。通常のコンストラクタを使ってこれを行うことができます。

let newString = String(result)

新しいSubstringクラスの詳細については、[Appleのドキュメント]を参照してください。 1

したがって、たとえばRangeの結果としてNSRegularExpressionを取得する場合は、次の拡張子を使用できます。

extension String {

    subscript(_ range: NSRange) -> String {
        let start = self.index(self.startIndex, offsetBy: range.lowerBound)
        let end = self.index(self.startIndex, offsetBy: range.upperBound)
        let subString = self[start..<end]
        return String(subString)
    }

}
19
gebirgsbärbel

Swift 4&5:

extension String {
  subscript(_ i: Int) -> String {
    let idx1 = index(startIndex, offsetBy: i)
    let idx2 = index(idx1, offsetBy: 1)
    return String(self[idx1..<idx2])
  }

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

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

どうやって使うのですか:

"abcde" [0]-> "a"

"abcde" [0 ... 2]-> "abc"

"abcde" [2 .. <4]-> "cd"

8
Souf ROCHDI

私は同じ初期反応がありました。私も、すべてのメジャーリリースで構文とオブジェクトがどのように劇的に変わるかに不満を感じました。

しかし、経験から、私はグローバルな視聴者を見ているなら避けられないマルチバイト文字を扱うような「変化」と戦おうとすることの結果に常に最終的に苦しむ方法を実感しました。

そこで私は、Appleのエンジニアが行った努力を認識し尊重し、彼らがこの「恐ろしい」アプローチを思いついたときに彼らの考え方を理解することによって私の役割を果たすことにしました。

あなたの生活を楽にするための単なる回避策である拡張を作成する代わりに(私はそれらが間違っていたり高価ではないと言っているのではありません)、どうやってStringsが今動作するように設計されているか考えないでください。

例えば、私はSwift 2.2で動いていたこのコードを持っていました:

let rString = cString.substringToIndex(2)
let gString = (cString.substringFromIndex(2) as NSString).substringToIndex(2)
let bString = (cString.substringFromIndex(4) as NSString).substringToIndex(2)

そして同じアプローチをうまくやろうとすることをあきらめた後。部分文字列を使用して、私はついに私が同じコードのこのバージョンで終わった双方向の集まりとして文字列を扱うという概念を理解しました:

let rString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let gString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let bString = String(cString.characters.prefix(2))

これが貢献することを願っています...

7
Rio Bautista

これは、開始インデックスと終了インデックスが指定されている場合に、指定された部分文字列の部分文字列を返す関数です。完全な参照のためにあなたは下記のリンクを訪れることができます。

func substring(string: String, fromIndex: Int, toIndex: Int) -> String? {
    if fromIndex < toIndex && toIndex < string.count /*use string.characters.count for Swift3*/{
        let startIndex = string.index(string.startIndex, offsetBy: fromIndex)
        let endIndex = string.index(string.startIndex, offsetBy: toIndex)
        return String(string[startIndex..<endIndex])
    }else{
        return nil
    }
}

これがSwiftで文字列操作を扱うために作成したブログ投稿へのリンクです。 Swiftでの文字列操作(Swift 4もカバー)

あるいはGithubでこの要旨を見ることができます

7
Nikesh Jha

私はSwift 3の新機能ですが、String(index)構文を類推してみると、indexは文字列に制約された "ポインタ"のようなもので、Intは独立したオブジェクトとして役立ちます。 base + offset構文を使用すると、次のコードでstringからi番目の文字を取得できます。 

let s = "abcdefghi"
let i = 2
print (s[s.index(s.startIndex, offsetBy:i)])
// print c

String(range)構文を使用して文字列から文字(インデックス)の範囲については、以下のコードでi番目からf番目の文字から取得できます。

let f = 6
print (s[s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 )])
//print cdefg

String.substring(range)を使用して文字列から部分文字列(range)を取得するには、以下のコードを使用して部分文字列を取得します。

print (s.substring (with:s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 ) ) )
//print cdefg

ノート: 

  1. I番目とf番目は0から始まります。

  2. F番目には、offsetBY:f + 1を使用します。これは、サブスクリプションの範囲に.. <(ハーフオープン演算子)が使用されているためです。f番目の位置は含まれません。

  3. もちろん、無効なインデックスなどの検証エラーを含める必要があります。

5
Nelson Mizutani

同じフラストレーション、これはそれほど難しいことではありません...

大きなテキストから部分文字列の位置を取得するこの例をまとめました。

//
// Play with finding substrings returning an array of the non-unique words and positions in text
//
//

import UIKit

let Bigstring = "Why is it so hard to find substrings in Swift3"
let searchStrs : Array<String>? = ["Why", "substrings", "Swift3"]

FindSubString(inputStr: Bigstring, subStrings: searchStrs)


func FindSubString(inputStr : String, subStrings: Array<String>?) ->    Array<(String, Int, Int)> {
    var resultArray : Array<(String, Int, Int)> = []
    for i: Int in 0...(subStrings?.count)!-1 {
        if inputStr.contains((subStrings?[i])!) {
            let range: Range<String.Index> = inputStr.range(of: subStrings![i])!
            let lPos = inputStr.distance(from: inputStr.startIndex, to: range.lowerBound)
            let uPos = inputStr.distance(from: inputStr.startIndex, to: range.upperBound)
            let element = ((subStrings?[i])! as String, lPos, uPos)
            resultArray.append(element)
        }
    }
    for words in resultArray {
        print(words)
    }
    return resultArray
}

( "Why"、0、3)( "substring"、26、36)( "Swift 3"、40、46)

4
Tall Dane

スイフト4

extension String {
    subscript(_ i: Int) -> String {
        let idx1 = index(startIndex, offsetBy: i)
        let idx2 = index(idx1, offsetBy: 1)
        return String(self[idx1..<idx2])
    }
}

let s = "hello"

s[0]    // h
s[1]    // e
s[2]    // l
s[3]    // l
s[4]    // o

私はこのための簡単な拡張を作りました(Swift 3)

extension String {
    func substring(location: Int, length: Int) -> String? {
        guard characters.count >= location + length else { return nil }
        let start = index(startIndex, offsetBy: location)
        let end = index(startIndex, offsetBy: location + length)
        return substring(with: start..<end)
    }
}
1
Lucas Algarra

上記に基づいて、私は文字を非印字文字で分割して非印字文字を落とす必要がありました。私は二つの方法を開発しました:

var str = "abc\u{1A}12345sdf"
let range1: Range<String.Index> = str.range(of: "\u{1A}")!
let index1: Int = str.distance(from: str.startIndex, to: range1.lowerBound)
let start = str.index(str.startIndex, offsetBy: index1)
let end = str.index(str.endIndex, offsetBy: -0)
let result = str[start..<end] // The result is of type Substring
let firstStr = str[str.startIndex..<range1.lowerBound]

私は上記の答えのいくつかを使ってまとめました。

Stringはコレクションなので、次のようにしました。

var fString = String()
for (n,c) in str.enumerated(){

*if c == "\u{1A}" {
    print(fString);
    let lString = str.dropFirst(n + 1)
    print(lString)
    break
   }
 fString += String(c)
}*

どちらが私にとって直感的でした。どれが一番いいですか?二人ともSwift 5で動作すると言うことはできません

1
Jeremy Andrews

私はかなり機械的な考えです。これが基本です。

スイフト4 スイフト5

  let t = "abracadabra"

  let start1 = t.index(t.startIndex, offsetBy:0)
  let   end1 = t.index(t.endIndex, offsetBy:-5)
  let start2 = t.index(t.endIndex, offsetBy:-5)
  let   end2 = t.index(t.endIndex, offsetBy:0)

  let t2 = t[start1 ..< end1]
  let t3 = t[start2 ..< end2]                

  //or a shorter form 

  let t4 = t[..<end1]
  let t5 = t[start2...]

  print("\(t2) \(t3) \(t)")
  print("\(t4) \(t5) \(t)")

  // result:
  // abraca dabra abracadabra

結果は部分文字列です。つまり、元の文字列の一部です。本格的な別の文字列を取得するには、単に使用します。

    String(t3)
    String(t4)

これは私が使っているものです:

    let mid = t.index(t.endIndex, offsetBy:-5)
    let firstHalf = t[..<mid]
    let secondHalf = t[mid...]
0
t1ser

Swift 4+

extension String {
    func take(_ n: Int) -> String {
        guard n >= 0 else {
            fatalError("n should never negative")
        }
        let index = self.index(self.startIndex, offsetBy: min(n, self.count))
        return String(self[..<index])
    }
}

最初のn文字のサブシーケンス、または文字列が短い場合は文字列全体を返します。 (から触発: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/take.html

例:

let text = "Hello, World!"
let substring = text.take(5) //Hello
0
Peter Kreinz

スイフト4

"サブストリング"( https://developer.Apple.com/documentation/Swift/substring ):

let greeting = "Hi there! It's Nice to meet you! ????"
let endOfSentence = greeting.index(of: "!")!
let firstSentence = greeting[...endOfSentence]
// firstSentence == "Hi there!"

拡張文字列の例:

private typealias HowDoYouLikeThatElonMusk = String
private extension HowDoYouLikeThatElonMusk {

    subscript(_ from: Character?, _ to: Character?, _ include: Bool) -> String? {
        if let _from: Character = from, let _to: Character = to {
            let dynamicSourceForEnd: String = (_from == _to ? String(self.reversed()) : self)
            guard let startOfSentence: String.Index = self.index(of: _from),
                let endOfSentence: String.Index = dynamicSourceForEnd.index(of: _to) else {
                return nil
            }

            let result: String = String(self[startOfSentence...endOfSentence])
            if include == false {
                guard result.count > 2 else {
                        return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        } else if let _from: Character = from {
            guard let startOfSentence: String.Index = self.index(of: _from) else {
                return nil
            }
            let result: String = String(self[startOfSentence...])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)...])
            }
            return result
        } else if let _to: Character = to {
            guard let endOfSentence: String.Index = self.index(of: _to) else {
                    return nil
            }
            let result: String = String(self[...endOfSentence])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        }
        return nil
    }
}

拡張子Stringの使用例

let source =                                   ">>>01234..56789<<<"
// include = true
var from =          source["3", nil, true]  //       "34..56789<<<"
var to =            source[nil, "6", true]  // ">>>01234..56"
var fromTo =        source["3", "6", true]  //       "34..56"
let notFound =      source["a", nil, true]  // nil
// include = false
from =              source["3", nil, false] //        "4..56789<<<"
to =                source[nil, "6", false] // ">>>01234..5"
fromTo =            source["3", "6", false] //        "4..5"
let outOfBounds =   source[".", ".", false] // nil

let str = "Hello, playground"
let hello = str[nil, ",", false] // "Hello"
0
CAHbl463

より一般的な実装を次に示します。

このテクニックはまだSwiftの標準を守るためにindexを使っており、完全なCharacterを意味します。

extension String
{
    func subString <R> (_ range: R) -> String? where R : RangeExpression, String.Index == R.Bound
    {
        return String(self[range])
    }

    func index(at: Int) -> Index
    {
        return self.index(self.startIndex, offsetBy: at)
    }
}

3文字目から部分文字列へ:

let item = "Fred looks funny"
item.subString(item.index(at: 2)...) // "ed looks funny"

キャメルsubStringは、StringではなくSubstringを返すことを示すために使いました。

0
Leslie Godwin