Jak stworzyć zasięg w Swift?

104

W Objective-c tworzymy zakres za pomocą NSRange

NSRange range;

Jak więc stworzyć zasięg w Swift?

vichhai
źródło
3
Do czego potrzebujesz tej gamy? Używa szybkich łańcuchów Range<String.Index>, ale czasami jest to konieczne do pracy z NSStringi NSRange, więc przydałoby się trochę więcej kontekstu. - Ale spójrz na stackoverflow.com/questions/24092884/… .
Martin R

Odpowiedzi:

258

Zaktualizowano dla Swift 4

Swift zakresy są bardziej złożone niż NSRangei 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ć.

Zamknięte zakresy: a...b

Ten operator zakresu tworzy zakres Swift, który zawiera zarówno element, jak a i element b, nawet jeśli bjest to maksymalna możliwa wartość dla typu (np Int.max.). Istnieją dwa różne typy zamkniętych zakresów: ClosedRangei CountableClosedRange.

1. 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 ClosedRangenie jest policzalne (tj. Nie jest zgodne z protokołem Sequence). Oznacza to, że nie możesz iterować po elementach za pomocą forpętli. Do tego potrzebujesz CountableClosedRange.

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

Zakresy półotwarte: a..<b

Ten operator zakresu obejmuje element, aale nie zawiera elementu b. Podobnie jak powyżej, istnieją dwa różne typy półotwartych zakresów: Rangei CountableRange.

1. 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ąć.

2. CountableRange

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

NSRange

Możesz (musisz) nadal używać NSRangew 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.

Zakresy z ciągami

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ć NSRangeoraz NSStringz 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"

Zakresy jednostronne: 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.startIndexlub str.endIndexna 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

Zobacz też:

Uwagi

  • Nie możesz użyć zakresu, który utworzyłeś z jednym ciągiem na innym ciągu.
  • Jak widać, zakresy ciągów są uciążliwe w Swift, ale sprawiają, że prawdopodobnie lepiej radzi sobie z emoji i innymi skalarami Unicode.

Dalsze badanie

Suragch
źródło
Mój komentarz dotyczy podrozdziału „Problem z NSRange”. NSStringwewnę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. NSRangeliczby oparte na 16-bitowych jednostkach kodu. W tym przykładzie, 3 jednostki kodu reprezentują tylko 2 znaki
kod inny,
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

lub po prostu

let myString = "Hello World"
let myRange = myString.startIndex..<myString.startIndex.advancedBy(5)
let mySubString = myString.substringWithRange(myRange)   // Hello
Leo Dabus
źródło
3

(1 .. <10)

zwroty...

Zakres = 1 .. <10

Victor Lin
źródło
Jak to odpowiada na pytanie?
Machavity
2

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

Dharmbir Singh
źródło
Widzę, że mają typ danych „Zakres”. Więc jak mogę tego używać?
vichhai
@vichhai Czy możesz podać więcej informacji, które chcesz wykorzystać?
Dharmbir Singh
Jak w celu c: zakres NSRange;
vichhai
1

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ą prefixi 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)
    }
}
matowe
źródło
1

Jeśli ktoś chce stworzyć obiekt NSRange może stworzyć jako:

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

stworzy to zakres z pozycją 0 i długością 5

Awangarda
źródło
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)
}
Abo3atef
źródło
0

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
dr OX
źródło
0

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
Mohammad Nurdin
źródło
0

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])
        }
    }
}
ScottyBlades
źródło