Làm thế nào để tạo phạm vi trong Swift?


103

Trong Objective-c, chúng tôi tạo phạm vi bằng cách sử dụng NSRange

NSRange range;

Vậy làm thế nào để tạo phạm vi trong Swift?


3
Bạn cần phạm vi để làm gì? Các chuỗi Swift sử dụng Range<String.Index>, nhưng đôi khi cần phải làm việc với NSStringNSRange, vì vậy một số ngữ cảnh khác sẽ hữu ích. - Nhưng hãy xem stackoverflow.com/questions/24092884/… .
Martin R

Câu trả lời:


256

Đã cập nhật cho Swift 4

Phạm vi Swift phức tạp hơn NSRangevà chúng không dễ dàng hơn trong Swift 3. Nếu bạn muốn cố gắng hiểu lý do đằng sau một số phức tạp này, hãy đọc phần nàyphần này . Tôi sẽ chỉ cho bạn cách tạo chúng và khi nào bạn có thể sử dụng chúng.

Phạm vi đóng: a...b

Toán tử phạm vi này tạo ra một phạm vi Swift bao gồm cả phần tử a phần tử b, ngay cả khi blà giá trị lớn nhất có thể cho một kiểu (như Int.max). Có hai loại phạm vi đóng khác nhau: ClosedRangeCountableClosedRange.

1. ClosedRange

Các phần tử của tất cả các phạm vi trong Swift đều có thể so sánh được (tức là chúng tuân theo giao thức có thể so sánh được). Điều đó cho phép bạn truy cập các phần tử trong phạm vi từ một tập hợp. Đây là một ví dụ:

let myRange: ClosedRange = 1...3

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

Tuy nhiên, a ClosedRangekhông thể đếm được (tức là nó không tuân theo giao thức Trình tự). Điều đó có nghĩa là bạn không thể lặp lại các phần tử bằng một forvòng lặp. Đối với điều đó bạn cần CountableClosedRange.

2. CountableClosedRange

Điều này tương tự như lần cuối cùng ngoại trừ bây giờ phạm vi cũng có thể được lặp lại.

let myRange: CountableClosedRange = 1...3

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

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

Phạm vi nửa mở: a..<b

Toán tử phạm vi này bao gồm phần tử anhưng không bao gồm phần tử b. Giống như trên, có hai loại phạm vi bán mở khác nhau: RangeCountableRange.

1. Range

Tương tự như vậy ClosedRange, bạn có thể truy cập các phần tử của một tập hợp bằng Range. Thí dụ:

let myRange: Range = 1..<3

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

Tuy nhiên, một lần nữa, bạn không thể lặp lại a Rangebởi vì nó chỉ có thể so sánh được, không thể sắp xếp được.

2. CountableRange

A CountableRangecho phép lặp lại.

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Đôi khi bạn vẫn có thể (phải) sử dụng Swift ( ví dụ: khi tạo các chuỗi phân bổ ), vì vậy sẽ rất hữu ích nếu bạn biết cách tạo một chuỗi .

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

Lưu ý rằng đây là vị trí và độ dài , không phải chỉ mục bắt đầu và chỉ mục kết thúc. Ví dụ ở đây có ý nghĩa tương tự với phạm vi Swift 3..<5. Tuy nhiên, vì các loại khác nhau, chúng không thể thay thế cho nhau.

Dải có chuỗi

Các .....<khai thác phạm vi là một cách viết tắt của việc tạo ra dãy. Ví dụ:

let myRange = 1..<3

Cách dài để tạo ra cùng một phạm vi sẽ là

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

Bạn có thể thấy rằng loại chỉ mục ở đây là Int. Tuy nhiên, điều đó không hiệu quả Stringvì Chuỗi được tạo từ các Ký tự và không phải tất cả các ký tự đều có cùng kích thước. (Đọc phần này để biết thêm thông tin.) Ví dụ: một biểu tượng cảm xúc như 😀, chiếm nhiều không gian hơn chữ "b".

Sự cố với NSRange

Hãy thử thử NSRangevà một NSStringbiểu tượng cảm xúc và bạn sẽ thấy ý tôi. Đau đầu.

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

Mặt cười sử dụng hai đơn vị mã UTF-16 để lưu trữ, vì vậy nó cho kết quả không mong muốn là không bao gồm chữ "d".

Giải pháp Swift

Bởi vì điều này, với Swift Strings bạn sử dụng Range<String.Index>, không Range<Int>. Chỉ số chuỗi được tính toán dựa trên một chuỗi cụ thể để nó biết liệu có bất kỳ biểu tượng cảm xúc hoặc cụm grapheme mở rộng nào hay không.

Thí dụ

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"

Dải một phía: a......b..<b

Trong Swift 4 mọi thứ đã được đơn giản hóa một chút. Bất cứ khi nào điểm bắt đầu hoặc điểm kết thúc của một phạm vi có thể được suy ra, bạn có thể bỏ qua.

Int

Bạn có thể sử dụng phạm vi số nguyên một phía để lặp qua các tập hợp. Dưới đây là một số ví dụ từ tài liệu .

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

Chuỗi

Điều này cũng hoạt động với phạm vi Chuỗi. Nếu bạn đang tạo một phạm vi bằng str.startIndexhoặc str.endIndexở một đầu, bạn có thể bỏ qua. Trình biên dịch sẽ suy ra nó.

Được

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

let myRange = ..<index    // Hello

Bạn có thể chuyển từ chỉ mục đến str.endIndex bằng cách sử dụng ...

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

Xem thêm:

Ghi chú

  • Bạn không thể sử dụng một dải ô bạn đã tạo với một chuỗi trên một chuỗi khác.
  • Như bạn có thể thấy, phạm vi chuỗi là một vấn đề trong Swift, nhưng chúng làm cho nó có thể xử lý tốt hơn với biểu tượng cảm xúc và các vô hướng Unicode khác.

Học cao hơn


Nhận xét của tôi liên quan đến tiểu mục "Vấn đề với NSRange". NSStringlưu trữ nội bộ các ký tự của nó ở dạng mã hóa UTF-16. Một vô hướng unicode đầy đủ là 21-bit. Ký tự khuôn mặt cười ( U+1F600) không thể được lưu trữ trong một đơn vị mã 16 bit duy nhất, vì vậy nó trải rộng trên 2. NSRangesố lượng dựa trên đơn vị mã 16 bit. Trong ví dụ này, 3 đơn vị mã xảy ra chỉ đại diện cho 2 ký tự
Mã Khác nhau

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

hoặc đơn giản

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


2

Sử dụng như thế này

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)

Đầu ra: Xin chào


Tôi thấy họ có Datatype "Phạm vi". Vậy tôi có thể sử dụng nó như thế nào?
vichhai

@vichhai Bạn có thể nói rõ hơn nơi bạn muốn sử dụng không?
Dharmbir Singh

Giống như trong mục tiêu c: Phạm vi NSRange;
vichhai

1

Tôi thấy ngạc nhiên rằng, ngay cả trong Swift 4, vẫn không có cách đơn giản nào để thể hiện một dải Chuỗi bằng Int. Các phương thức Chuỗi duy nhất cho phép bạn cung cấp Int như một cách để lấy chuỗi con theo phạm vi là prefixsuffix.

Sẽ rất hữu ích nếu bạn có trong tay một số tiện ích chuyển đổi để chúng ta có thể nói chuyện như NSRange khi nói chuyện với một Chuỗi. Đây là một tiện ích lấy vị trí và độ dài, giống như NSRange và trả về 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
}

Ví dụ, "hello".range(0,1)"Range<String.Index>ôm ký tự đầu tiên của "hello". Như một phần thưởng, tôi đã cho phép các vị trí phủ định: "hello".range(-1,1)"Range<String.Index>ký tự cuối cùng bao trùm "hello".

Nó cũng hữu ích khi chuyển đổi a Range<String.Index>thành NSRange, đối với những thời điểm bạn phải nói chuyện với Cocoa (ví dụ: trong việc xử lý các phạm vi thuộc tính NSAttributedString). Swift 4 cung cấp một cách gốc để làm điều đó:

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

Do đó, chúng tôi có thể viết một tiện ích khác, nơi chúng tôi đi trực tiếp từ vị trí và độ dài chuỗi đến một NSRange:

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

1

Nếu ai muốn tạo đối tượng NSRange có thể tạo như:

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

điều này sẽ tạo ra phạm vi với vị trí 0 và độ dài 5


0

Tôi đã tạo tiện ích mở rộng sau:

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

ví dụ sử dụng:

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

Bạn có thể sử dụng như thế này

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

như trong

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

Tôi muốn làm điều này:

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

Nhưng thật không may, tôi không thể viết một chỉ số con của riêng mình vì chỉ số bị ghét chiếm không gian tên.

Tuy nhiên, chúng tôi có thể làm điều này:

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

Chỉ cần thêm cái này vào dự án của bạn:

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

0
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)
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.