Bagaimana cara membuat jangkauan di Swift?


104

Di Objective-c kita membuat range dengan menggunakan NSRange

NSRange range;

Jadi bagaimana cara membuat range di Swift?


3
Untuk apa Anda membutuhkan jangkauan? String Swift digunakan Range<String.Index>, tapi terkadang perlu untuk bekerja dengan NSStringdan NSRange, jadi beberapa konteks lainnya akan berguna. - Tapi lihat stackoverflow.com/questions/24092884/… .
Martin R

Jawaban:


258

Diperbarui untuk Swift 4

Rentang Swift lebih kompleks daripada NSRange, dan tidak menjadi lebih mudah di Swift 3. Jika Anda ingin mencoba memahami alasan di balik beberapa kerumitan ini, baca ini dan ini . Saya hanya akan menunjukkan cara membuatnya dan kapan Anda mungkin menggunakannya.

Rentang Tertutup: a...b

Operator rentang ini membuat rentang Swift yang menyertakan elemen a dan elemen b, meskipun bnilai maksimum yang mungkin untuk suatu tipe (suka Int.max). Ada dua jenis rentang tertutup: ClosedRangedan CountableClosedRange.

1. ClosedRange

Elemen dari semua rentang di Swift sebanding (yaitu, mereka sesuai dengan protokol Comparable). Itu memungkinkan Anda mengakses elemen dalam rentang dari koleksi. Berikut ini contohnya:

let myRange: ClosedRange = 1...3

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

Namun, a ClosedRangetidak dapat dihitung (yaitu, tidak sesuai dengan protokol Urutan). Itu berarti Anda tidak dapat mengulang elemen dengan forloop. Untuk itu Anda membutuhkan file CountableClosedRange.

2. CountableClosedRange

Ini mirip dengan yang terakhir kecuali sekarang rentangnya juga dapat diulang.

let myRange: CountableClosedRange = 1...3

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

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

Rentang Setengah Terbuka: a..<b

Operator rentang ini menyertakan elemen atetapi bukan elemen b. Seperti di atas, ada dua jenis rentang setengah terbuka: Rangedan CountableRange.

1. Range

Seperti halnya ClosedRange, Anda dapat mengakses elemen koleksi dengan file Range. Contoh:

let myRange: Range = 1..<3

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

Namun, sekali lagi, Anda tidak dapat mengulangi a Rangekarena hanya dapat dibandingkan, tidak dapat dilangkah.

2. CountableRange

A CountableRangememungkinkan iterasi.

let myRange: CountableRange = 1..<3

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

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

NSRange

Anda NSRangekadang-kadang masih dapat (harus) menggunakan di Swift (saat membuat string yang diatribusikan , misalnya), jadi akan sangat membantu untuk mengetahui cara membuatnya.

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

Perhatikan bahwa ini adalah lokasi dan panjang , bukan indeks awal dan indeks akhir. Contoh di sini serupa artinya dengan rentang Swift 3..<5. Namun, karena jenisnya berbeda, mereka tidak dapat dipertukarkan.

Berkisar dengan Strings

Operator ...dan ..<jangkauan adalah cara cepat untuk membuat rentang. Sebagai contoh:

let myRange = 1..<3

Cara tangan panjang untuk membuat rentang yang sama adalah

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

Anda dapat melihat bahwa tipe indeks di sini adalah Int. Itu tidak berhasil String, karena String terbuat dari Karakter dan tidak semua karakter memiliki ukuran yang sama. (Baca ini untuk info lebih lanjut.) Emoji seperti 😀, misalnya, membutuhkan lebih banyak tempat daripada huruf "b".

Masalah dengan NSRange

Cobalah bereksperimen dengan NSRangedan NSStringdengan emoji dan Anda akan melihat apa yang saya maksud. Sakit kepala.

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"!?

Wajah tersenyum membutuhkan dua unit kode UTF-16 untuk disimpan, sehingga memberikan hasil yang tidak diharapkan dari tidak menyertakan "d".

Solusi Cepat

Karena itu, dengan Swift Strings yang Anda gunakan Range<String.Index>, bukan Range<Int>. Indeks String dihitung berdasarkan string tertentu sehingga mengetahui apakah ada emoji atau cluster grafik yang diperluas.

Contoh

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"

Rentang satu sisi: a...dan ...bdan..<b

Di Swift 4 hal disederhanakan sedikit. Kapan pun titik awal atau akhir suatu rentang dapat disimpulkan, Anda dapat membiarkannya.

Int

Anda dapat menggunakan rentang integer satu sisi untuk mengulang koleksi. Berikut beberapa contoh dari dokumentasinya .

// 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...

Tali

Ini juga berfungsi dengan rentang String. Jika Anda membuat rentang dengan str.startIndexatau str.endIndexdi salah satu ujung, Anda bisa membiarkannya. Kompilator akan menyimpulkannya.

Diberikan

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

let myRange = ..<index    // Hello

Anda dapat beralih dari indeks ke str.endIndex dengan menggunakan ...

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

Lihat juga:

Catatan

  • Anda tidak dapat menggunakan rentang yang Anda buat dengan satu string pada string berbeda.
  • Seperti yang Anda lihat, rentang String menyebalkan di Swift, tetapi mereka memungkinkan untuk menangani emoji dan skalar Unicode lainnya dengan lebih baik.

Pelajaran lanjutan


Komentar saya berkaitan dengan subbagian "Masalah dengan NSRange". NSStringmenyimpan karakternya secara internal dalam pengkodean UTF-16. Skalar unicode penuh adalah 21-bit. Karakter wajah yang menyeringai ( U+1F600) tidak dapat disimpan dalam satu unit kode 16-bit, sehingga tersebar 2. NSRangedihitung berdasarkan unit kode 16-bit. Dalam contoh ini, 3 unit kode kebetulan hanya mewakili 2 karakter
Kode Berbeda

25

Xcode 8 beta 2 • Swift 3

let myString = "Hello World"
let myRange = myString.startIndex..<myString.index(myString.startIndex, offsetBy: 5)
let mySubString = myString.substring(with: myRange)   // Hello

Xcode 7 • Swift 2.0

let myString = "Hello World"
let myRange = Range<String.Index>(start: myString.startIndex, end: myString.startIndex.advancedBy(5))

let mySubString = myString.substringWithRange(myRange)   // Hello

atau sederhananya

let myString = "Hello World"
let myRange = myString.startIndex..<myString.startIndex.advancedBy(5)
let mySubString = myString.substringWithRange(myRange)   // Hello


2

Gunakan seperti ini

var start = str.startIndex // Start at the string's start index
var end = advance(str.startIndex, 5) // Take start index and advance 5 characters forward
var range: Range<String.Index> = Range<String.Index>(start: start,end: end)

let firstFiveDigit =  str.substringWithRange(range)

print(firstFiveDigit)

Hasil: Halo


Saya melihat mereka memiliki "Range" Datatype. Jadi bagaimana saya bisa menggunakannya?
vichhai

@vichhai Bisakah Anda menjelaskan lebih lanjut di mana Anda ingin menggunakan?
Dharmbir Singh

Seperti dalam obyektif c: kisaran NSRange;
vichhai

1

Saya merasa mengejutkan bahwa, bahkan di Swift 4, masih tidak ada cara asli sederhana untuk mengekspresikan rentang String menggunakan Int. Satu-satunya metode String yang memungkinkan Anda menyediakan Int sebagai cara untuk mendapatkan substring menurut range adalah prefixdan suffix.

Berguna untuk memiliki beberapa utilitas konversi, sehingga kita dapat berbicara seperti NSRange saat berbicara dengan String. Berikut adalah utilitas yang mengambil lokasi dan panjang, seperti NSRange, dan mengembalikan Range<String.Index>:

func range(_ start:Int, _ length:Int) -> Range<String.Index> {
    let i = self.index(start >= 0 ? self.startIndex : self.endIndex,
        offsetBy: start)
    let j = self.index(i, offsetBy: length)
    return i..<j
}

Misalnya, "hello".range(0,1)"adalah Range<String.Index>merangkul karakter pertama "hello". Sebagai bonus, saya mengizinkan lokasi negatif: "hello".range(-1,1)"adalah Range<String.Index>pelukan karakter terakhir "hello".

Berguna juga untuk mengonversi a Range<String.Index>menjadi NSRange, untuk saat-saat ketika Anda harus berbicara dengan Cocoa (misalnya, dalam menangani rentang atribut NSAttributedString). Swift 4 menyediakan cara asli untuk melakukan itu:

let nsrange = NSRange(range, in:s) // where s is the string

Dengan demikian kita dapat menulis utilitas lain di mana kita pergi langsung dari lokasi dan panjang String ke NSRange:

extension String {
    func nsRange(_ start:Int, _ length:Int) -> NSRange {
        return NSRange(self.range(start,length), in:self)
    }
}

1

Jika ada yang ingin membuat objek NSRange bisa dibuat sebagai:

let range: NSRange = NSRange.init(location: 0, length: 5)

ini akan menciptakan jarak dengan posisi 0 dan panjang 5


1
func replace(input: String, start: Int,lenght: Int, newChar: Character) -> String {
    var chars = Array(input.characters)

    for i in start...lenght {
        guard i < input.characters.count else{
            break
        }
        chars[i] = newChar
    }
    return String(chars)
}

0

Saya membuat ekstensi berikut:

extension String {
    func substring(from from:Int, to:Int) -> String? {
        if from<to && from>=0 && to<self.characters.count {
            let rng = self.startIndex.advancedBy(from)..<self.startIndex.advancedBy(to)
            return self.substringWithRange(rng)
        } else {
            return nil
        }
    }
}

contoh penggunaan:

print("abcde".substring(from: 1, to: 10)) //nil
print("abcde".substring(from: 2, to: 4))  //Optional("cd")
print("abcde".substring(from: 1, to: 0))  //nil
print("abcde".substring(from: 1, to: 1))  //nil
print("abcde".substring(from: -1, to: 1)) //nil

0

Anda bisa menggunakan seperti ini

let nsRange = NSRange(location: someInt, length: someInt)

seperti dalam

let myNSString = bigTOTPCode as NSString //12345678
let firstDigit = myNSString.substringWithRange(NSRange(location: 0, length: 1)) //1
let secondDigit = myNSString.substringWithRange(NSRange(location: 1, length: 1)) //2
let thirdDigit = myNSString.substringWithRange(NSRange(location: 2, length: 4)) //3456

0

Aku ingin melakukan ini:

print("Hello"[1...3])
// out: Error

Tapi sayangnya, saya tidak bisa menulis subskrip saya sendiri karena yang dibenci mengambil ruang nama.

Namun kami dapat melakukan ini:

print("Hello"[range: 1...3])
// out: ell 

Cukup tambahkan ini ke proyek Anda:

extension String {
    subscript(range: ClosedRange<Int>) -> String {
        get {
            let start = String.Index(utf16Offset: range.lowerBound, in: self)
            let end = String.Index(utf16Offset: range.upperBound, in: self)
            return String(self[start...end])
        }
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.