1. ホーム
  2. swift

[解決済み] Swiftで範囲を作成する方法は?

2022-05-13 11:05:07

質問

Objective-cでは、NSRangeを使用して範囲を作成します。

NSRange range;

では、Swiftで範囲を作るにはどうすればいいのでしょうか?

どのように解決するのですか?

Swift 4にアップデートしました。

Swift の範囲は、より複雑な NSRange よりも複雑で、Swift 3でも簡単ではありませんでした。この複雑さのいくつかの背後にある理由を理解しようとする場合、以下を読んでください。 この この . これらの作成方法と、どのような場合に使用するかを紹介します。

クローズド・レンジ a...b

これは 範囲演算子 は Swift の範囲を作成し、その範囲には要素 a 要素 b であっても b がその型に可能な最大値であっても(例えば Int.max ). 閉じた範囲には2つのタイプがあります。 ClosedRangeCountableClosedRange .

1. ClosedRange

Swift のすべての範囲の要素は比較可能です (つまり、Comparable プロトコルに準拠します)。そのため、コレクションから範囲内の要素にアクセスすることができます。以下はその例です。

let myRange: ClosedRange = 1...3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]

しかし ClosedRange は可算ではない(つまり、Sequenceプロトコルに準拠しない)。これはつまり for ループを使うことはできません。そのためには CountableClosedRange .

2. CountableClosedRange

これは最後のものと似ていますが、今度は範囲も反復させることができます。

let myRange: CountableClosedRange = 1...3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]

for index in myRange {
    print(myArray[index])
}

ハーフオープンレンジ a..<b

この範囲演算子には、要素 a しかし ではない 要素 b . 上記と同様に、半開放型の範囲には2種類あります。 RangeCountableRange .

1. Range

と同様に ClosedRange と同じように、コレクションの要素にアクセスするために Range . 例

let myRange: Range = 1..<3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]

を反復処理することはできませんが、繰り返します。 Range は比較可能なだけで、 stridable ではないからです。

2. CountableRange

A CountableRange は反復を可能にする。

let myRange: CountableRange = 1..<3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]

for index in myRange {
    print(myArray[index])
}

NSRange

この場合でも NSRange を使うことができます(Swiftで 属性付きの文字列 を作るときなど)、その作り方を知っておくと便利です。

let myNSRange = NSRange(location: 3, length: 2)

なお、これはロケーションと 長さ であり、開始インデックスと終了インデックスではないことに注意してください。ここでの例は、Swift の範囲と同じような意味を持つ 3..<5 . しかし、型が異なるので、互換性はありません。

文字列を含む範囲

このような .....< 範囲演算子は、範囲を作成するための省略記法である。例えば

let myRange = 1..<3

同じ範囲を作るための長い手の方法は次のようになります。

let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3

ここでインデックスの型が Int . ではうまくいきません。 String なぜなら、文字列は文字でできていて、すべての文字が同じ大きさではないからです。(読む この をご覧ください)。例えば、「?」のような絵文字は、「b"」という文字よりも大きなスペースを必要とします。

NSRangeの問題

を試してみてください。 NSRangeNSString を絵文字で入力すれば、私の言っていることがわかるでしょう。頭が痛くなる。

let myNSRange = NSRange(location: 1, length: 3)

let myNSString: NSString = "abcde"
myNSString.substring(with: myNSRange) // "bcd"

let myNSString2: NSString = "a????cde"
myNSString2.substring(with: myNSRange) // "????c"    Where is the "d"!?

スマイリーフェイスは格納するために2つのUTF-16コードユニットを取るので、"d"を含まないという予想外の結果を出します。

Swift の解決策

このため、Swiftの文字列では Range<String.Index> ではなく Range<Int> . 文字列インデックスは、特定の文字列に基づいて計算され、絵文字や拡張書記素クラスタがあるかどうかを知ることができます。

var myString = "abcde"
let start = myString.index(myString.startIndex, offsetBy: 1)
let end = myString.index(myString.startIndex, offsetBy: 4)
let myRange = start..<end
myString[myRange] // "bcd"

myString = "a????cde"
let start2 = myString.index(myString.startIndex, offsetBy: 1)
let end2 = myString.index(myString.startIndex, offsetBy: 4)
let myRange2 = start2..<end2
myString[myRange2] // "????cd"

片側だけの範囲 a......b そして ..<b

Swift 4では、少し簡略化されました。範囲の開始点または終了点が推論できるときはいつでも、それを省くことができます。

int

コレクションを反復処理するために、片側だけの整数範囲を使用することができます。以下はその例です。 ドキュメンテーション .

// iterate from index 2 to the end of the array
for name in names[2...] {
    print(name)
}

// iterate from the beginning of the array to index 2
for name in names[...2] {
    print(name)
}

// iterate from the beginning of the array up to but not including index 2
for name in names[..<2] {
    print(name)
}

// the range from negative infinity to 5. You can't iterate forward
// over this because the starting point in unknown.
let range = ...5
range.contains(7)   // false
range.contains(4)   // true
range.contains(-1)  // true

// You can iterate over this but it will be an infinate loop 
// so you have to break out at some point.
let range = 5...

文字列

これはStringの範囲でも動作します。もし、範囲を str.startIndex または str.endIndex を片方の端につけても、そのままにしておくことができます。コンパイラはそれを推論する。

与えられた

var str = "Hello, playground"
let index = str.index(str.startIndex, offsetBy: 5)

let myRange = ..<index    // Hello

インデックスからstr.endIndexに移動するためには ...

var str = "Hello, playground"
let index = str.index(str.endIndex, offsetBy: -10)
let myRange = index...        // playground

こちらもご覧ください。

注意事項

  • ある文字列で作成した範囲を別の文字列で使用することはできません。
  • お分かりのように、文字列の範囲は Swift では面倒ですが、絵文字や他の Unicode のスカラーをよりよく扱うことができる可能性を持っています。

さらなる研究