จะสร้างช่วงใน Swift ได้อย่างไร


105

ใน Objective-c เราสร้างช่วงโดยใช้ NSRange

NSRange range;

แล้วจะสร้างช่วงใน Swift ได้อย่างไร?


3
คุณต้องการช่วงสำหรับอะไร? สตริง Swift ใช้Range<String.Index>แต่บางครั้งก็จำเป็นต้องใช้งานNSStringและNSRangeบริบทเพิ่มเติมบางส่วนจะเป็นประโยชน์ - แต่มีลักษณะที่stackoverflow.com/questions/24092884/...
Martin R

คำตอบ:


261

อัปเดตสำหรับ Swift 4

ช่วงที่สวิฟท์มีความซับซ้อนมากกว่าNSRangeและพวกเขาไม่ได้รับการใด ๆ ได้ง่ายขึ้นในสวิฟท์ 3. หากคุณต้องการที่จะพยายามที่จะเข้าใจเหตุผลที่อยู่เบื้องหลังบางส่วนของความซับซ้อนนี้อ่านนี้และนี้ ฉันจะแสดงวิธีสร้างและเวลาที่คุณจะใช้

ช่วงปิด: a...b

ตัวดำเนินการช่วงนี้สร้างช่วง Swift ซึ่งมีทั้งองค์ประกอบa และองค์ประกอบbแม้ว่าbจะเป็นค่าสูงสุดที่เป็นไปได้สำหรับประเภท (like Int.max) ช่วงปิดมีสองประเภท: ClosedRangeและCountableClosedRange.

1. ClosedRange

องค์ประกอบของช่วงทั้งหมดใน Swift สามารถเปรียบเทียบได้ (กล่าวคือเป็นไปตามโปรโตคอลที่เปรียบเทียบได้) ที่ช่วยให้คุณเข้าถึงองค์ประกอบในช่วงจากคอลเลกชัน นี่คือตัวอย่าง:

let myRange: ClosedRange = 1...3

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

อย่างไรก็ตาม a ClosedRangeไม่สามารถนับได้ (กล่าวคือไม่เป็นไปตามโปรโตคอลลำดับ) นั่นหมายความว่าคุณไม่สามารถวนซ้ำองค์ประกอบด้วยการ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องค์ประกอบ เช่นเดียวกับข้างต้นมีช่วงครึ่งเปิดสองประเภทที่แตกต่างกัน: RangeและCountableRange.

1. Range

เช่นเดียวกับClosedRangeคุณสามารถเข้าถึงองค์ประกอบของคอลเลกชันด้วยไฟล์Range. ตัวอย่าง:

let myRange: Range = 1..<3

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

อย่างไรก็ตามอีกครั้งคุณไม่สามารถทำซ้ำได้Rangeเนื่องจากเป็นเพียงการเทียบเคียงเท่านั้นไม่สามารถทำได้

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)

โปรดทราบว่านี่คือตำแหน่งและความยาวไม่ใช่ดัชนีเริ่มต้นและดัชนีสิ้นสุด 3..<5ตัวอย่างที่นี่จะคล้ายกันในความหมายในช่วงที่สวิฟท์ อย่างไรก็ตามเนื่องจากประเภทต่างกันจึงไม่สามารถใช้แทนกันได้

ช่วงที่มีสตริง

ตัวดำเนินการ...และ..<ช่วงเป็นวิธีการสร้างช่วงแบบชวเลข ตัวอย่างเช่น:

let myRange = 1..<3

วิธีที่ยาวในการสร้างช่วงเดียวกันจะเป็น

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

Intคุณจะเห็นว่าชนิดของดัชนีที่นี่คือ แม้ว่าจะใช้ไม่ได้ผลStringเนื่องจาก Strings ทำจากอักขระและอักขระบางตัวจะมีขนาดไม่เท่ากัน (อ่านนี้สำหรับข้อมูลเพิ่มเติม.) อีโมจิเหมือน😀ตัวอย่างเช่นใช้พื้นที่มากกว่าตัวอักษร "B"

ปัญหากับ NSRange

ลองทดสอบด้วยNSRangeและNSStringอีโมจิและคุณจะเห็นว่าผมหมายถึง ปวดหัว

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

หน้ายิ้มใช้รหัส UTF-16 สองหน่วยในการจัดเก็บดังนั้นจึงให้ผลลัพธ์ที่ไม่คาดคิดจากการไม่รวม "d"

โซลูชัน Swift

ด้วยเหตุนี้กับสวิฟท์ Strings คุณใช้ไม่ได้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

ดูสิ่งนี้ด้วย:

หมายเหตุ

  • คุณไม่สามารถใช้ช่วงที่คุณสร้างด้วยสตริงเดียวในสตริงอื่น
  • อย่างที่คุณเห็นช่วง String เป็นความเจ็บปวดใน Swift แต่ก็ทำให้สามารถจัดการกับอีโมจิและสเกลาร์ Unicode อื่น ๆ ได้ดีขึ้น

ศึกษาเพิ่มเติม


ความคิดเห็นของฉันเกี่ยวข้องกับส่วนย่อย "Problem with NSRange" NSStringเก็บอักขระไว้ภายในในการเข้ารหัส UTF-16 สเกลาร์ยูนิโคดแบบเต็มคือ 21 บิต อักขระหน้ายิ้ม ( U+1F600) ไม่สามารถจัดเก็บไว้ในหน่วยรหัส 16 บิตเดียวได้ดังนั้นจึงมีการกระจายมากกว่า 2 NSRangeหน่วยตามหน่วยรหัส 16 บิต ในตัวอย่างนี้หน่วยรหัส 3 หน่วยแทนอักขระเพียง 2 ตัว
รหัสต่าง

25

Xcode 8 เบต้า 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

หรือเพียงแค่

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


2

ใช้แบบนี้

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)

เอาท์พุท:สวัสดี


ฉันเห็นว่ามีประเภทข้อมูล "ช่วง" ฉันจะใช้มันได้อย่างไร?
vichhai

@vichhai คุณสามารถอธิบายเพิ่มเติมได้ไหมว่าคุณต้องการใช้ที่ไหน?
Dharmbir Singh

เหมือนในวัตถุประสงค์ c: ช่วง NSRange;
vichhai

1

ฉันพบว่ามันน่าแปลกใจที่แม้แต่ใน Swift 4 ก็ยังไม่มีวิธีง่ายๆในการแสดงช่วง String โดยใช้ Int เพียงวิธี String ที่ช่วยให้คุณจัดหา Int เป็นวิธีของการได้รับสตริงย่อยโดยช่วงเป็นและprefixsuffix

มันมีประโยชน์ที่จะมียูทิลิตี้การแปลงอยู่ในมือเพื่อที่เราจะสามารถพูดได้เหมือน NSRange เมื่อพูดกับสตริง นี่คือยูทิลิตี้ที่ใช้ตำแหน่งและความยาวเช่นเดียวกับ NSRange และส่งกลับ a 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
}

ยกตัวอย่างเช่น"hello".range(0,1)"เป็นกอดตัวอักษรตัวแรกของRange<String.Index> "hello"เป็นโบนัส, ฉันได้รับอนุญาตสถานที่เชิงลบ: "hello".range(-1,1)"เป็นกอดตัวอักษรตัวสุดท้ายของRange<String.Index>"hello"

นอกจากนี้ยังมีประโยชน์ในการแปลง a Range<String.Index>เป็น NSRange สำหรับช่วงเวลาที่คุณต้องคุยกับ Cocoa (ตัวอย่างเช่นในการจัดการกับช่วงแอตทริบิวต์ NSAttributedString) Swift 4 มีวิธีดั้งเดิมในการทำเช่นนั้น:

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

ดังนั้นเราสามารถเขียนยูทิลิตี้อื่นที่เราไปโดยตรงจากตำแหน่ง String และความยาวไปยัง NSRange:

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

1

หากใครต้องการสร้าง NSRange object สามารถสร้างเป็น:

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

สิ่งนี้จะสร้างช่วงด้วยตำแหน่ง 0 และความยาว 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

ฉันสร้างส่วนขยายต่อไปนี้:

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

ตัวอย่างการใช้งาน:

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

คุณสามารถใช้แบบนี้

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

เช่นเดียวกับใน

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

ฉันต้องการทำสิ่งนี้:

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

แต่น่าเสียดายที่ฉันเขียนตัวห้อยของตัวเองไม่ได้เพราะอันที่เกลียดนั้นกินเนื้อที่ชื่อ

อย่างไรก็ตามเราสามารถทำได้:

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

เพียงเพิ่มสิ่งนี้ในโครงการของคุณ:

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])
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.