Odpowiedzi:
Zaktualizowano dla Swift 4
Swift zakresy są bardziej złożone niż NSRange
i nie były łatwiejsze w Swift 3. Jeśli chcesz spróbować zrozumieć przyczyny tej złożoności, przeczytaj to i to . Pokażę ci tylko, jak je tworzyć i kiedy możesz ich używać.
a...b
Ten operator zakresu tworzy zakres Swift, który zawiera zarówno element, jak a
i element b
, nawet jeśli b
jest to maksymalna możliwa wartość dla typu (np Int.max
.). Istnieją dwa różne typy zamkniętych zakresów: ClosedRange
i CountableClosedRange
.
ClosedRange
Elementy wszystkich zakresów w Swift są porównywalne (tj. Są zgodne z protokołem Comparable). To umożliwia dostęp do elementów w zakresie z kolekcji. Oto przykład:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
Jednak a ClosedRange
nie jest policzalne (tj. Nie jest zgodne z protokołem Sequence). Oznacza to, że nie możesz iterować po elementach za pomocą for
pętli. Do tego potrzebujesz CountableClosedRange
.
CountableClosedRange
Jest to podobne do poprzedniego, z wyjątkiem tego, że zakres można również iterować.
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
Ten operator zakresu obejmuje element, a
ale nie zawiera elementu b
. Podobnie jak powyżej, istnieją dwa różne typy półotwartych zakresów: Range
i CountableRange
.
Range
Podobnie jak w przypadku ClosedRange
, możesz uzyskać dostęp do elementów kolekcji za pomocą pliku Range
. Przykład:
let myRange: Range = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
Ponownie jednak nie możesz iterować po pliku Range
ponieważ jest on tylko porównywalny, a nie dający się usunąć.
CountableRange
A CountableRange
pozwala na iterację.
let myRange: CountableRange = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
for index in myRange {
print(myArray[index])
}
Możesz (musisz) nadal używać NSRange
w Swift ( na przykład podczas tworzenia przypisanych ciągów ), więc warto wiedzieć, jak je utworzyć.
let myNSRange = NSRange(location: 3, length: 2)
Zauważ, że jest to lokalizacja i długość , a nie indeks początkowy i końcowy. Poniższy przykład ma podobne znaczenie do serii Swift 3..<5
. Jednak ze względu na różne typy nie można ich stosować zamiennie.
Operatory ...
i ..<
zakresów to skrótowy sposób tworzenia zakresów. Na przykład:
let myRange = 1..<3
Długa droga do stworzenia tego samego zakresu byłaby
let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3
Widać, że typ indeksu to Int
. To jednak nie działa String
, ponieważ ciągi składają się ze znaków i nie wszystkie znaki są tego samego rozmiaru. (Przeczytaj to aby uzyskać więcej informacji). Na przykład emoji, takie jak 😀, zajmuje więcej miejsca niż litera „b”.
Problem z NSRange
Wypróbować NSRange
oraz NSString
z emotikonami, a zobaczysz co mam na myśli. Bół głowy.
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"!?
Buźka zajmuje do przechowywania dwie jednostki kodu UTF-16, więc daje nieoczekiwany rezultat nieuwzględniania litery „d”.
Szybkie rozwiązanie
Z tego powodu, ze Swift Strings użyć Range<String.Index>
, nie Range<Int>
. Indeks ciągu jest obliczany na podstawie określonego ciągu, dzięki czemu wie, czy są jakieś emoji lub rozszerzone klastry grafemów.
Przykład
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...
i...b
i..<b
W Swift 4 rzeczy zostały nieco uproszczone. Zawsze, gdy można wywnioskować punkt początkowy lub końcowy zakresu, możesz go pominąć.
Int
Do iteracji po kolekcjach można używać jednostronnych zakresów liczb całkowitych. Oto kilka przykładów z dokumentacji .
// 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...
Strunowy
Działa to również z zakresami typu String. Jeśli tworzysz zakres z str.startIndex
lub str.endIndex
na jednym końcu, możesz to zostawić. Kompilator wywnioskuje to.
Dany
var str = "Hello, playground"
let index = str.index(str.startIndex, offsetBy: 5)
let myRange = ..<index // Hello
Możesz przejść z indeksu do str.endIndex przy użyciu ...
var str = "Hello, playground"
let index = str.index(str.endIndex, offsetBy: -10)
let myRange = index... // playground
Uwagi
NSString
wewnętrznie przechowuje swoje znaki w kodowaniu UTF-16. Pełny skalar Unicode jest 21-bitowy. Znak uśmiechniętej twarzy ( U+1F600
) nie może być przechowywany w pojedynczej 16-bitowej jednostce kodu, więc jest rozłożony na 2. NSRange
liczby oparte na 16-bitowych jednostkach kodu. W tym przykładzie, 3 jednostki kodu reprezentują tylko 2 znaki
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
lub po prostu
let myString = "Hello World"
let myRange = myString.startIndex..<myString.startIndex.advancedBy(5)
let mySubString = myString.substringWithRange(myRange) // Hello
Użyj w ten sposób
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)
Wyjście: Hello
Zaskakujące jest dla mnie to, że nawet w języku Swift 4 nadal nie ma prostego natywnego sposobu wyrażenia zakresu String za pomocą Int. Jedynymi metodami typu String, które pozwalają podać Int jako sposób uzyskania podciągu według zakresu, są prefix
i suffix
.
Przydatne jest mieć pod ręką kilka narzędzi do konwersji, abyśmy mogli mówić jak NSRange podczas mówienia do String. Oto narzędzie, które przyjmuje lokalizację i długość, podobnie jak NSRange, i zwraca 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
}
Na przykład, "hello".range(0,1)"
to Range<String.Index>
objęcie pierwszego znaku "hello"
. Jako bonus, zezwoliłem na negatywne lokalizacje: "hello".range(-1,1)"
jest Range<String.Index>
to ostatnia postać "hello"
.
Przydatne jest również przekonwertowanie a Range<String.Index>
na NSRange, w tych momentach, kiedy musisz rozmawiać z Cocoa (na przykład w przypadku radzenia sobie z zakresami atrybutów NSAttributedString). Swift 4 zapewnia natywny sposób na zrobienie tego:
let nsrange = NSRange(range, in:s) // where s is the string
Możemy zatem napisać inne narzędzie, w którym przechodzimy bezpośrednio z lokalizacji String i długości do NSRange:
extension String {
func nsRange(_ start:Int, _ length:Int) -> NSRange {
return NSRange(self.range(start,length), in:self)
}
}
Stworzyłem następujące rozszerzenie:
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
}
}
}
przykład użycia:
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
Możesz używać w ten sposób
let nsRange = NSRange(location: someInt, length: someInt)
jak w
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
Chce to zrobić:
print("Hello"[1...3])
// out: Error
Ale niestety nie mogę napisać własnego indeksu, ponieważ ten nienawidzony zajmuje miejsce na nazwisko.
Możemy to jednak zrobić:
print("Hello"[range: 1...3])
// out: ell
Po prostu dodaj to do swojego projektu:
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])
}
}
}
Range<String.Index>
, ale czasami jest to konieczne do pracy zNSString
iNSRange
, więc przydałoby się trochę więcej kontekstu. - Ale spójrz na stackoverflow.com/questions/24092884/… .