Jak działa string substring w języku Swift

Aktualizowałem część mojego starego kodu i odpowiedzi za pomocą Swift 3, ale kiedy dotarłem do SWIFT Strings i indeksowania z podciągami rzeczy stały się mylące.

W szczególności próbowałem:

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5)
let prefix = str.substringWithRange(prefixRange)

Gdzie w drugiej linijce pojawił się następujący błąd

Wartość typu "String" nie ma elementu "substringWithRange"

Widzę, że String ma teraz następujące metody:

str.substring(to: String.Index)
str.substring(from: String.Index)
str.substring(with: Range<String.Index>)

To było naprawdę mylące mnie na początku, więc ja zaczął grać wokół Indeks i zakres. Jest to kolejne pytanie i odpowiedź dla podłańcucha. Dodaję odpowiedź poniżej, aby pokazać, w jaki sposób są one używane.

Author: Suragch, 2016-09-24

11 answers

Tutaj wpisz opis obrazka

Wszystkie poniższe przykłady używają

var str = "Hello, playground"

Swift 4

Struny mają dość duży remont w Swift 4. Po pobraniu fragmentu łańcucha otrzymujemy typ Substring zamiast String. Dlaczego? Ciągi są typami wartości w języku Swift. Oznacza to, że jeśli użyjesz jednego ciągu znaków, aby utworzyć nowy, musisz go skopiować. Jest to dobre dla stabilności (nikt inny nie zmieni tego bez Twojej wiedzy), ale złe dla wydajności.

Podłańcuch jest natomiast odniesieniem do oryginalnego ciągu znaków, z którego pochodzi. Oto zdjęcie z dokumentacji ilustrujące to.

Kopiowanie nie jest konieczne, więc jest znacznie bardziej wydajne w użyciu. Wyobraź sobie jednak, że masz dziesięć znaków z miliona łańcuchów znaków. Ponieważ Podłańcuch odwołuje się do łańcucha znaków, system musiałby trzymać cały łańcuch tak długo, jak cały Podłańcuch znajduje się wokół niego. Tak więc, gdy skończysz manipulować Podłańcuchem, przekonwertuj go na ciąg znaków.

let myString = String(mySubstring)

To skopiuje tylko podłańcuch i stary łańcuch może zostać usunięty. Podciągi (jako typ) mają być krótkotrwałe.

Kolejną dużą poprawą w Swift 4 jest to, że ciągi są kolekcjami (znowu). Oznacza to, że cokolwiek możesz zrobić z kolekcją, możesz zrobić z łańcuchem znaków (użyj indeksów dolnych, iteruj nad znakami, filtruj, itp.).

Następujące przykłady pokaż jak uzyskać podłańcuch w języku Swift.

Pobieranie podciągów

Można uzyskać podłańcuch z ciągu znaków za pomocą indeksów dolnych lub wielu innych metod (na przykład, prefix, suffix, split). Nadal musisz użyć String.Index, a nie Int indeksu dla zakresu. (Zobacz moja druga odpowiedź jeśli potrzebujesz pomocy z tym.)

Początek ciągu

Możesz użyć indeksu dolnego (zwróć uwagę na jednostronny zakres Swift 4):

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello

Lub prefix:

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str.prefix(upTo: index) // Hello

Albo jeszcze łatwiej:

let mySubstring = str.prefix(5) // Hello

Koniec łańcucha

Korzystanie z indeksów dolnych:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str[index...] // playground

Lub suffix:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground

Albo jeszcze łatwiej:

let mySubstring = str.suffix(10) // playground

Zauważ, że używając suffix(from: index) musiałem liczyć od końca używając -10. Nie jest to konieczne, gdy używa się tylko suffix(x), które zajmuje tylko ostatnie x znaki ciągu.

Zakres w ciągu

Ponownie używamy po prostu indeksów dolnych tutaj.

let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end

let mySubstring = str[range]  // play

Konwertowanie Substring do String

Nie zapomnij, że kiedy będziesz gotowy do zapisania fragmentu łańcucha, przekonwertuj go na String, aby pamięć starego łańcucha mogła zostać wyczyszczona.

let myString = String(mySubstring)

Używając rozszerzenia indeksu Int?

Po przeczytaniu artykułu waham się użyć rozszerzenia indeksu Int struny w Swift 3 przez Airspeed Velocity i Ole Begemann. Chociaż w Swift 4 ciągi są kolekcjami, zespół Swift celowo nie używał indeksów Int. Nadal jest String.Index. Ma to związek z tym, że znaki Swift składają się z różnej liczby punktów kodowych Unicode. Rzeczywisty indeks musi być jednoznacznie obliczony dla każdego ciągu.

Muszę powiedzieć, że mam nadzieję, że zespół Swift znajdzie sposób, by w przyszłości uciec. Ale dopóki nie zdecyduję się użyć ich API. Pomaga mi to pamiętać, że manipulacje ciągami to nie tylko proste wyszukiwanie indeksu Int.
 585
Author: Suragch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-07 01:22:32

Jestem naprawdę sfrustrowany modelem dostępu strunowego Swifta: wszystko musi być Index. Chcę tylko uzyskać dostęp do i-tego znaku łańcucha za pomocą Int, a nie indeksu niezgrabnego i postępującego (co zdarza się zmieniać z każdym głównym wydaniem). Więc zrobiłem rozszerzenie do String:

extension String {
    func index(from: Int) -> Index {
        return self.index(startIndex, offsetBy: from)
    }

    func substring(from: Int) -> String {
        let fromIndex = index(from: from)
        return substring(from: fromIndex)
    }

    func substring(to: Int) -> String {
        let toIndex = index(from: to)
        return substring(to: toIndex)
    }

    func substring(with r: Range<Int>) -> String {
        let startIndex = index(from: r.lowerBound)
        let endIndex = index(from: r.upperBound)
        return substring(with: startIndex..<endIndex)
    }
}

let str = "Hello, playground"
print(str.substring(from: 7))         // playground
print(str.substring(to: 5))           // Hello
print(str.substring(with: 7..<11))    // play
 139
Author: Code Different,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-24 14:55:17

Swift 4 Extension:

extension String { 
    subscript(_ range: CountableRange<Int>) -> String { 
        let idx1 = index(startIndex, offsetBy: max(0, range.lowerBound))
        let idx2 = index(startIndex, offsetBy: min(self.count, range.upperBound))
        return String(self[idx1..<idx2])
    }    
}       

Użycie:

let s = "hello"
s[0..<3] // "hel"
s[3..<s.count] // "lo"

Lub unicode:

let s = ""
s[0..<1] // ""
 38
Author: Lou Zell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-07 23:51:29

Swift 4

W swift 4 String odpowiada Collection. Zamiast substring, powinniśmy teraz użyć subscript., więc jeśli chcesz wyciąć tylko słowo "play" z "Hello, playground", możesz to zrobić tak:

var str = "Hello, playground"
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let result = str[start..<end] // The result is of type Substring

To ciekawe wiedzieć, że to da Substring zamiast String. Jest to szybkie i wydajne, ponieważ Substring dzieli swoje przechowywanie z oryginalnym ciągiem. Jednak dzielenie pamięci w ten sposób może również łatwo prowadzić do wycieków pamięci.

Dlatego powinieneś skopiować uzyskaj wynik w nowy ciąg, gdy chcesz wyczyścić oryginalny ciąg. Można to zrobić za pomocą zwykłego konstruktora:

let newString = String(result)

Więcej informacji na temat nowej klasy Substring można znaleźć w [Dokumentacja Apple].1

Więc, jeśli na przykład otrzymasz Range w wyniku NSRegularExpression, możesz użyć następującego rozszerzenia:

extension String {

    subscript(_ range: NSRange) -> String {
        let start = self.index(self.startIndex, offsetBy: range.lowerBound)
        let end = self.index(self.startIndex, offsetBy: range.upperBound)
        let subString = self[start..<end]
        return String(subString)
    }

}
 14
Author: gebirgsbärbel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-18 17:28:18

Miałem taką samą reakcję początkową. Ja też byłem sfrustrowany tym, jak składnia i Obiekty zmieniają się tak drastycznie w każdym większym wydaniu.

Jednak zdałem sobie sprawę z tego, że zawsze w końcu cierpię konsekwencje walki z "zmianą", jak radzenie sobie z wielobajtowymi postaciami, co jest nieuniknione, jeśli patrzysz na globalną publiczność.

Postanowiłem więc docenić i uszanować wysiłki inżynierów Apple i zrobić swoją część, rozumiejąc ich sposób myślenia, kiedy wymyślili to "przerażające" podejście.

Zamiast tworzyć rozszerzenia, które są tylko obejściem ułatwiającym życie (nie mówię, że są złe lub drogie), dlaczego nie dowiedzieć się, jak struny są teraz zaprojektowane do działania.

Na przykład miałem ten kod, który działał na Swift 2.2:

let rString = cString.substringToIndex(2)
let gString = (cString.substringFromIndex(2) as NSString).substringToIndex(2)
let bString = (cString.substringFromIndex(4) as NSString).substringToIndex(2)

I po rezygnacji z prób uzyskania tego samego podejścia działającego np. przy użyciu Podłańcuchów, w końcu zrozumiałem koncepcję traktowania ciągów jako zbioru dwukierunkowego dla którego skończyłem z tą wersją tego samego kodu:

let rString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let gString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let bString = String(cString.characters.prefix(2))
Mam nadzieję, że to pomoże...
 7
Author: Rio Bautista,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-11 03:19:41

Jestem nowy w Swift 3, ale patrząc na składnię String (index) dla analogii myślę, że index jest jak "wskaźnik" ograniczony do string i Int może pomóc jako niezależny obiekt. Używając składni base + offset, wtedy możemy otrzymać i-ten znak z łańcucha o kodzie poniżej:

let s = "abcdefghi"
let i = 2
print (s[s.index(s.startIndex, offsetBy:i)])
// print c

Dla zakresu znaków (indeksów) od string używając składni String (range) możemy uzyskać od i-th do f-TH znaków z poniższym kodem:

let f = 6
print (s[s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 )])
//print cdefg

Dla fragmentu (zakresu) łańcucha używając Sznurka.substring (zakres) możemy uzyskać substring używając poniższego kodu:

print (s.substring (with:s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 ) ) )
//print cdefg

Uwagi:

  1. I-th i F-TH zaczynają się od 0.

  2. Do f-th, używam offsetBY: f + 1, ponieważ zakres użytkowania subskrypcji ..

  3. Oczywiście musi zawierać błędy walidacji, takie jak invalid index.

 5
Author: Nelson Mizutani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-13 23:15:42

Oto funkcja, która zwraca podłańcuch danego podłańcucha, gdy podane są indeksy początku i końca. Pełne informacje można znaleźć pod linkami podanymi poniżej.

func substring(string: String, fromIndex: Int, toIndex: Int) -> String? {
    if fromIndex < toIndex && toIndex < string.count /*use string.characters.count for swift3*/{
        let startIndex = string.index(string.startIndex, offsetBy: fromIndex)
        let endIndex = string.index(string.startIndex, offsetBy: toIndex)
        return String(string[startIndex..<endIndex])
    }else{
        return nil
    }
}

Oto link do wpisu na blogu, który stworzyłem, aby poradzić sobie z manipulacją strunami w swift. manipulacja strunami w języku swift (obejmuje również swift 4)

Możesz też zobaczyć ten gist na github

 5
Author: Nikesh Jha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-01 06:04:50

Ta sama frustracja, to nie powinno być takie trudne...

Skompilowałem ten przykład pobierania pozycji dla podłańcuchów z większego tekstu:

//
// Play with finding substrings returning an array of the non-unique words and positions in text
//
//

import UIKit

let Bigstring = "Why is it so hard to find substrings in Swift3"
let searchStrs : Array<String>? = ["Why", "substrings", "Swift3"]

FindSubString(inputStr: Bigstring, subStrings: searchStrs)


func FindSubString(inputStr : String, subStrings: Array<String>?) ->    Array<(String, Int, Int)> {
    var resultArray : Array<(String, Int, Int)> = []
    for i: Int in 0...(subStrings?.count)!-1 {
        if inputStr.contains((subStrings?[i])!) {
            let range: Range<String.Index> = inputStr.range(of: subStrings![i])!
            let lPos = inputStr.distance(from: inputStr.startIndex, to: range.lowerBound)
            let uPos = inputStr.distance(from: inputStr.startIndex, to: range.upperBound)
            let element = ((subStrings?[i])! as String, lPos, uPos)
            resultArray.append(element)
        }
    }
    for words in resultArray {
        print(words)
    }
    return resultArray
}

Zwraca ("Dlaczego", 0, 3)) ("substraty", 26, 36) ("Swift3", 40, 46)

 4
Author: Tall Dane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-11 20:40:32

Stworzyłem proste rozszerzenie dla tego (Swift 3)

extension String {
    func substring(location: Int, length: Int) -> String? {
        guard characters.count >= location + length else { return nil }
        let start = index(startIndex, offsetBy: location)
        let end = index(startIndex, offsetBy: location + length)
        return substring(with: start..<end)
    }
}
 1
Author: Lucas Algarra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-28 18:52:00

Swift 4

extension String {
    subscript(_ i: Int) -> String {
        let idx1 = index(startIndex, offsetBy: i)
        let idx2 = index(idx1, offsetBy: 1)
        return String(self[idx1..<idx2])
    }
}

let s = "hello"

s[0]    // h
s[1]    // e
s[2]    // l
s[3]    // l
s[4]    // o
 1
Author: iOS Calendar View OnMyProfile,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-15 03:59:50

Swift 4

"Substring" ( https://developer.apple.com/documentation/swift/substring):

let greeting = "Hi there! It's nice to meet you! "
let endOfSentence = greeting.index(of: "!")!
let firstSentence = greeting[...endOfSentence]
// firstSentence == "Hi there!"

Przykład ciągu rozszerzenia:

private typealias HowDoYouLikeThatElonMusk = String
private extension HowDoYouLikeThatElonMusk {

    subscript(_ from: Character?, _ to: Character?, _ include: Bool) -> String? {
        if let _from: Character = from, let _to: Character = to {
            let dynamicSourceForEnd: String = (_from == _to ? String(self.reversed()) : self)
            guard let startOfSentence: String.Index = self.index(of: _from),
                let endOfSentence: String.Index = dynamicSourceForEnd.index(of: _to) else {
                return nil
            }

            let result: String = String(self[startOfSentence...endOfSentence])
            if include == false {
                guard result.count > 2 else {
                        return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        } else if let _from: Character = from {
            guard let startOfSentence: String.Index = self.index(of: _from) else {
                return nil
            }
            let result: String = String(self[startOfSentence...])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)...])
            }
            return result
        } else if let _to: Character = to {
            guard let endOfSentence: String.Index = self.index(of: _to) else {
                    return nil
            }
            let result: String = String(self[...endOfSentence])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        }
        return nil
    }
}

Przykład użycia ciągu rozszerzenia:

let source =                                   ">>>01234..56789<<<"
// include = true
var from =          source["3", nil, true]  //       "34..56789<<<"
var to =            source[nil, "6", true]  // ">>>01234..56"
var fromTo =        source["3", "6", true]  //       "34..56"
let notFound =      source["a", nil, true]  // nil
// include = false
from =              source["3", nil, false] //        "4..56789<<<"
to =                source[nil, "6", false] // ">>>01234..5"
fromTo =            source["3", "6", false] //        "4..5"
let outOfBounds =   source[".", ".", false] // nil

let str = "Hello, playground"
let hello = str[nil, ",", false] // "Hello"
 0
Author: CAHbl463,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-07 00:12:37