Wie erstelle ich eine Reichweite in Swift?

103

In Objective-c erstellen wir einen Bereich mit NSRange

NSRange range;

Wie erstelle ich eine Reichweite in Swift?

Vichhai
quelle
3
Wofür brauchen Sie die Reichweite? Schnelle Zeichenfolgen werden verwendet Range<String.Index>, aber manchmal ist es notwendig, mit NSStringund zu arbeiten NSRange, sodass etwas mehr Kontext nützlich wäre. - Schauen Sie sich aber stackoverflow.com/questions/24092884/… an .
Martin R

Antworten:

256

Aktualisiert für Swift 4

Swift-Bereiche sind komplexer als NSRangeund wurden in Swift 3 nicht einfacher. Wenn Sie versuchen möchten, die Gründe für diese Komplexität zu verstehen, lesen Sie dies und das . Ich zeige Ihnen nur, wie Sie sie erstellen und wann Sie sie verwenden könnten.

Geschlossene Bereiche: a...b

Dieser Bereichsoperator erstellt einen Swift-Bereich, der sowohl Element a als auch Element enthält b, auch wenn dies bder maximal mögliche Wert für einen Typ (wie Int.max) ist. Es gibt zwei verschiedene Arten von geschlossenen Bereichen: ClosedRangeund CountableClosedRange.

1. ClosedRange

Die Elemente aller Bereiche in Swift sind vergleichbar (dh sie entsprechen dem Comparable-Protokoll). Auf diese Weise können Sie aus einer Sammlung auf die Elemente im Bereich zugreifen. Hier ist ein Beispiel:

let myRange: ClosedRange = 1...3

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

A ClosedRangeist jedoch nicht zählbar (dh es entspricht nicht dem Sequenzprotokoll). Das heißt, Sie können die Elemente nicht mit einer forSchleife durchlaufen . Dafür brauchen Sie die CountableClosedRange.

2. CountableClosedRange

Dies ähnelt dem letzten, außer dass der Bereich jetzt auch wiederholt werden kann.

let myRange: CountableClosedRange = 1...3

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

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

Halboffene Bereiche: a..<b

Dieser Bereichsoperator enthält ein Element, ajedoch kein Element b. Wie oben gibt es zwei verschiedene Arten von halboffenen Bereichen: Rangeund CountableRange.

1. Range

Wie bei ClosedRangekönnen Sie mit einem auf die Elemente einer Sammlung zugreifen Range. Beispiel:

let myRange: Range = 1..<3

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

Auch hier können Sie nicht über a iterieren, Rangeda es nur vergleichbar und nicht schrittfähig ist.

2. CountableRange

A CountableRangeermöglicht die Iteration.

let myRange: CountableRange = 1..<3

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

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

NSRange

Sie können (müssen) NSRangemanchmal noch in Swift verwenden ( z. B. wenn Sie zugeordnete Zeichenfolgen erstellen), daher ist es hilfreich zu wissen, wie man eine erstellt.

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

Beachten Sie, dass dies Position und Länge ist , nicht Startindex und Endindex. Das Beispiel hier hat eine ähnliche Bedeutung wie der Swift-Bereich 3..<5. Da die Typen jedoch unterschiedlich sind, sind sie nicht austauschbar.

Bereiche mit Saiten

Die Operatoren ...und ..<range sind eine Kurzform zum Erstellen von Bereichen. Beispielsweise:

let myRange = 1..<3

Der lange Weg, um den gleichen Bereich zu erstellen, wäre

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

Sie können sehen, dass der Indextyp hier ist Int. Dies funktioniert jedoch nicht String, da Zeichenfolgen aus Zeichen bestehen und nicht alle Zeichen dieselbe Größe haben. (Lesen Sie dies für weitere Informationen.) Ein Emoji wie 😀 benötigt beispielsweise mehr Platz als der Buchstabe "b".

Problem mit NSRange

Versuchen Sie, mit NSRangeund NSStringmit Emoji zu experimentieren, und Sie werden sehen, was ich meine. Kopfschmerzen.

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

Das Smiley-Gesicht benötigt zwei UTF-16-Codeeinheiten zum Speichern, sodass das unerwartete Ergebnis entsteht, dass das "d" nicht enthalten ist.

Schnelle Lösung

Aus diesem Grund verwenden Sie Swift Strings Range<String.Index>nicht Range<Int>. Der String-Index wird basierend auf einem bestimmten String berechnet, damit er weiß, ob Emoji oder erweiterte Graphemcluster vorhanden sind.

Beispiel

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"

Einseitige Bereiche: a...und ...bund..<b

In Swift 4 wurden die Dinge etwas vereinfacht. Wann immer der Start- oder Endpunkt eines Bereichs abgeleitet werden kann, können Sie ihn weglassen.

Int

Sie können einseitige Ganzzahlbereiche verwenden, um Sammlungen zu durchlaufen. Hier einige Beispiele aus der Dokumentation .

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

Dies funktioniert auch mit String-Bereichen. Wenn Sie einen Bereich mit str.startIndexoder str.endIndexan einem Ende erstellen, können Sie ihn weglassen. Der Compiler wird darauf schließen.

Gegeben

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

let myRange = ..<index    // Hello

Sie können mithilfe von vom Index zum str.endIndex wechseln ...

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

Siehe auch:

Anmerkungen

  • Sie können einen Bereich, den Sie mit einer Zeichenfolge erstellt haben, nicht für eine andere Zeichenfolge verwenden.
  • Wie Sie sehen können, sind String-Bereiche in Swift ein Problem, aber sie machen es möglicherweise möglich, besser mit Emoji und anderen Unicode-Skalaren umzugehen.

Weitere Studie

Suragch
quelle
Mein Kommentar bezieht sich auf den Unterabschnitt "Problem mit NSRange". NSStringspeichert seine Zeichen intern in UTF-16-Codierung. Ein vollständiger Unicode-Skalar ist 21 Bit. Das grinsende Zeichen ( U+1F600) kann nicht in einer einzelnen 16-Bit-Codeeinheit gespeichert werden, daher ist es auf 2 NSRangeZählungen basierend auf 16-Bit-Codeeinheiten verteilt. In diesem Beispiel repräsentieren 3 Codeeinheiten zufällig nur 2 Zeichen
Code Different
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

oder einfach

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

(1 .. <10)

kehrt zurück...

Bereich = 1 .. <10

Victor Lin
quelle
Wie beantwortet dies die Frage?
Machavity
2

Verwenden Sie so

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)

Ausgabe: Hallo

Dharmbir Singh
quelle
Ich sehe, dass sie den Datentyp "Range" haben. Wie kann ich es verwenden?
Vichhai
@vichhai Können Sie näher erläutern, wo Sie verwenden möchten?
Dharmbir Singh
Wie in Ziel c: NSRange-Bereich;
Vichhai
1

Ich finde es überraschend, dass es selbst in Swift 4 noch keine einfache native Möglichkeit gibt, einen String-Bereich mit Int auszudrücken. Die einzigen String-Methoden, mit denen Sie ein Int angeben können, um einen Teilstring nach Bereich zu erhalten, sind prefixund suffix.

Es ist nützlich, einige Konvertierungsprogramme zur Hand zu haben, damit wir wie NSRange sprechen können, wenn wir mit einem String sprechen. Hier ist ein Dienstprogramm, das genau wie NSRange einen Speicherort und eine Länge annimmt und Folgendes zurückgibt 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
}

Zum Beispiel "hello".range(0,1)"ist das Range<String.Index>Umarmen des ersten Charakters von "hello". Als Bonus habe ich auszuschließende Standorte erlaubt: "hello".range(-1,1)"die Range<String.Index>das letzte Zeichen umfassen "hello".

Es ist auch nützlich, a Range<String.Index>in einen NSRange zu konvertieren , wenn Sie mit Cocoa sprechen müssen (z. B. im Umgang mit NSAttributedString-Attributbereichen). Swift 4 bietet eine native Möglichkeit, dies zu tun:

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

Wir können also ein anderes Dienstprogramm schreiben, bei dem wir direkt von einem String-Speicherort und einer String-Länge zu einem NSRange wechseln:

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

Wenn jemand ein NSRange-Objekt erstellen möchte, kann er Folgendes erstellen:

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

Dadurch wird ein Bereich mit Position 0 und Länge 5 erstellt

Van
quelle
0

Ich habe die folgende Erweiterung erstellt:

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

Anwendungsbeispiel:

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
Dr. OX
quelle
0

Sie können so verwenden

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

wie in

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
Mohammad Nurdin
quelle
0

Ich möchte das machen:

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

Aber leider kann ich keinen eigenen Index schreiben, weil der Abscheuliche den Namensraum einnimmt.

Wir können dies jedoch tun:

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

Fügen Sie dies einfach Ihrem Projekt hinzu:

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])
        }
    }
}
ScottyBlades
quelle
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)
}
Abo3atef
quelle