Получить n-й символ строки в языке программирования Swift

338

Как я могу получить n-й символ строки? Я попытался использовать скобки ([]) без удачи.

var string = "Hello, world!"

var firstChar = string[0] // Throws error

ОШИБКА: "индекс" недоступен: не может подписать строку с Int, см. Комментарий к документации для обсуждения

  • 1
    Похоже, что сообщение об ошибке «не может подписать строку с помощью Int, см. Комментарий к документации для обсуждения» относится к github.com/apple/swift/blob/master/stdlib/public/core/…
Теги:
string
collections
character

38 ответов

475
Лучший ответ

Внимание: см. Ответ Leo Dabus для правильной реализации для Swift 4.

Swift 4

Тип Substring был введен в Swift 4, чтобы сделать подстроки быстрее и эффективнее, используя хранилище с исходной строкой, чтобы возвращались функции подстрочного индекса.

Попробуйте здесь

extension String {
  subscript (i: Int) -> Character {
    return self[index(startIndex, offsetBy: i)]
  }
  subscript (bounds: CountableRange<Int>) -> Substring {
    let start = index(startIndex, offsetBy: bounds.lowerBound)
    let end = index(startIndex, offsetBy: bounds.upperBound)
    return self[start ..< end]
  }
  subscript (bounds: CountableClosedRange<Int>) -> Substring {
    let start = index(startIndex, offsetBy: bounds.lowerBound)
    let end = index(startIndex, offsetBy: bounds.upperBound)
    return self[start ... end]
  }
  subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring {
    let start = index(startIndex, offsetBy: bounds.lowerBound)
    let end = index(endIndex, offsetBy: -1)
    return self[start ... end]
  }
  subscript (bounds: PartialRangeThrough<Int>) -> Substring {
    let end = index(startIndex, offsetBy: bounds.upperBound)
    return self[startIndex ... end]
  }
  subscript (bounds: PartialRangeUpTo<Int>) -> Substring {
    let end = index(startIndex, offsetBy: bounds.upperBound)
    return self[startIndex ..< end]
  }
}
extension Substring {
  subscript (i: Int) -> Character {
    return self[index(startIndex, offsetBy: i)]
  }
  subscript (bounds: CountableRange<Int>) -> Substring {
    let start = index(startIndex, offsetBy: bounds.lowerBound)
    let end = index(startIndex, offsetBy: bounds.upperBound)
    return self[start ..< end]
  }
  subscript (bounds: CountableClosedRange<Int>) -> Substring {
    let start = index(startIndex, offsetBy: bounds.lowerBound)
    let end = index(startIndex, offsetBy: bounds.upperBound)
    return self[start ... end]
  }
  subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring {
    let start = index(startIndex, offsetBy: bounds.lowerBound)
    let end = index(endIndex, offsetBy: -1)
    return self[start ... end]
  }
  subscript (bounds: PartialRangeThrough<Int>) -> Substring {
    let end = index(startIndex, offsetBy: bounds.upperBound)
    return self[startIndex ... end]
  }
  subscript (bounds: PartialRangeUpTo<Int>) -> Substring {
    let end = index(startIndex, offsetBy: bounds.upperBound)
    return self[startIndex ..< end]
  }
}

Чтобы преобразовать Substring в String, вы можете просто создать String String(string[0..2]), но вы должны сделать это только в том случае, если вы планируете поддерживать подстроку. В противном случае, более эффективно поддерживать Substring.

Было бы здорово, если бы кто-то мог найти хороший способ объединить эти два расширения в один. Я попытался расширить StringProtocol без успеха, потому что index метод там не существует.

Swift 3:

extension String {
  subscript (i: Int) -> Character {
    return self[index(startIndex, offsetBy: i)]
  }
  subscript (i: Int) -> String {
    return String(self[i] as Character)
  }
  subscript (r: Range<Int>) -> String {
    let start = index(startIndex, offsetBy: r.lowerBound)
    let end = index(startIndex, offsetBy: r.upperBound)
    return self[Range(start ..< end)]
  }
}

Почему это не встроено?

Apple предоставляет следующее объяснение (найдено здесь):

Подписные строки с целыми числами недоступны.

Понятие " i й символ в строке" имеет разные интерпретации в разных библиотеках и системных компонентах. Правильная интерпретация должна быть выбрана в соответствии с используемым случаем и задействованными API, поэтому String не может быть индексировано с целым числом.

Swift предоставляет несколько различных способов доступа к символьным данным, хранящимся внутри строк.

  • String.utf8 - это набор кодовых блоков UTF-8 в строке. Используйте этот API при преобразовании строки в UTF-8. Большинство API-интерфейсов POSIX обрабатывают строки в единицах кода UTF-8.

  • String.utf16 - это набор кодовых блоков UTF-16 в строке. Большинство API-интерфейсов Cocoa и Cocoa touch обрабатывают строки в единицах кода UTF-16. Например, экземпляры NSRange используемые с NSAttributedString и NSRegularExpression хранят подстроки и длины в единицах кода UTF-16.

  • String.unicodeScalars - это набор String.unicodeScalars Unicode. Используйте этот API при выполнении низкоуровневых манипуляций с символьными данными.

  • String.characters - это совокупность расширенных кластеров графемы, которые являются приближением воспринимаемых пользователем символов.

Обратите внимание, что при обработке строк, содержащих текст, читаемый человеком, следует избегать в любом случае пошаговую обработку. Вместо этого используйте высокоуровневые языковые алгоритмы Unicode, например String.localizedStandardCompare(), String.localizedLowercaseString, String.localizedStandardRangeOfString() и т.д.

  • 0
    Правильно, но это не делает адаптацию диапазона для нарезки строки, которую вышеуказанное решение решает так элегантно.
  • 4
    Cannot find an overload for 'advance' that accepts an argument list of type '(String.Index, T)' ... String.Index и Int несовместимы.
Показать ещё 22 комментария
296

Swift 4.2 и ранее

let str = "abcdef"
str[1 ..< 3] // returns "bc"
str[5] // returns "f"
str[80] // returns ""
str.substring(fromIndex: 3) // returns "def"
str.substring(toIndex: str.length - 2) // returns "abcd"

Вам нужно будет добавить это расширение String в свой проект (он полностью протестирован):

extension String {

  var length: Int {
    return count
  }

  subscript (i: Int) -> String {
    return self[i ..< i + 1]
  }

  func substring(fromIndex: Int) -> String {
    return self[min(fromIndex, length) ..< length]
  }

  func substring(toIndex: Int) -> String {
    return self[0 ..< max(0, toIndex)]
  }

  subscript (r: Range<Int>) -> String {
    let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)),
                                        upper: min(length, max(0, r.upperBound))))
    let start = index(startIndex, offsetBy: range.lowerBound)
    let end = index(start, offsetBy: range.upperBound - range.lowerBound)
    return String(self[start ..< end])
  }

}

Несмотря на то, что у Swift всегда была проблема с этой проблемой (без расширения строки, которое я приводил ниже), я по-прежнему настоятельно рекомендую использовать расширение. Зачем? Потому что это спасло мне десятки часов болезненной миграции из ранних версий Swift, где синтаксис String менялся почти каждый выпуск, но все, что мне нужно было сделать, это обновить реализацию расширения, а не рефакторинг всей производственной линии на 300 тыс. Строк кода приложение. Сделай свой выбор.

let str = "Hello, world!"
let index = str.index(str.startIndex, offsetBy: 4)
str[index] // returns Character 'o'

let endIndex = str.index(str.endIndex, offsetBy:-2)
str[index ..< endIndex] // returns String "o, worl"

String(str.suffix(from: index)) // returns String "o, world!"
String(str.prefix(upTo: index)) // returns String "Hell"
  • 0
    изменить range.upperBound - range.lowerBound на range.count
  • 0
    Это не часть первоначального вопроса, но ... было бы неплохо, если бы это поддерживало назначение. Например, s [i] = "a" :).
Показать ещё 2 комментария
116

Нет индексации с использованием целых чисел, используя только String.Index. В основном с линейной сложностью. Вы также можете создавать диапазоны от String.Index и получать подстроки, используя их.

Swift 3.0

let firstChar = someString[someString.startIndex]
let lastChar = someString[someString.index(before: someString.endIndex)]
let charAtIndex = someString[someString.index(someString.startIndex, offsetBy: 10)]

let range = someString.startIndex..<someString.index(someString.startIndex, offsetBy: 10)
let substring = someString[range]

Swift 2.x

let firstChar = someString[someString.startIndex]
let lastChar = someString[someString.endIndex.predecessor()]
let charAtIndex = someString[someString.startIndex.advanceBy(10)]

let range = someString.startIndex..<someString.startIndex.advanceBy(10)
let subtring = someString[range]

Обратите внимание, что вы никогда не сможете использовать индекс (или диапазон), созданный из одной строки в другую строку

let index10 = someString.startIndex.advanceBy(10)

//will compile
//sometimes it will work but sometimes it will crash or result in undefined behaviour
let charFromAnotherString = anotherString[index10]
  • 7
    String индексы уникальны для строки. Это связано с тем, что разные строки могут иметь разные многокомпонентные Characters UTF-16 и / или в разных позициях, поэтому индексы единиц UTF-16 не будут совпадать, могут выходить за пределы конца или точки внутри многозначного Character UTF-16.
  • 0
    @Zaph Это очевидно.
Показать ещё 4 комментария
112

Я просто придумал это опрятное решение

var firstChar = Array(string)[0]
  • 2
    Это хороший быстрый обходной путь для (обычного) случая, когда вы знаете, что у вас есть строки в кодировке UTF8 или ASCII. Просто убедитесь, что строки никогда не будут в кодировке, которая использует более одного байта.
  • 41
    Это кажется крайне неэффективным, поскольку вы копируете всю строку только для того, чтобы получить первый символ. Используйте строку [строка. startIndex] вместо 0, как указал Султан.
Показать ещё 5 комментариев
82

Swift 4.1 или позже

Вы можете расширить Swift 4 StringProtocol, чтобы сделать индекс доступным также для подстрок. Примечание. Из-за предложения SE-0191 ограничение расширения на IndexDistance == Int может быть удалено:

extension StringProtocol {

    subscript(offset: Int) -> Element {
        return self[index(startIndex, offsetBy: offset)]
    }

    subscript(_ range: CountableRange<Int>) -> SubSequence {
        return prefix(range.lowerBound + range.count)
            .suffix(range.count)
    }
    subscript(range: CountableClosedRange<Int>) -> SubSequence {
        return prefix(range.lowerBound + range.count)
            .suffix(range.count)
    }

    subscript(range: PartialRangeThrough<Int>) -> SubSequence {
        return prefix(range.upperBound.advanced(by: 1))
    }
    subscript(range: PartialRangeUpTo<Int>) -> SubSequence {
        return prefix(range.upperBound)
    }
    subscript(range: PartialRangeFrom<Int>) -> SubSequence {
        return suffix(Swift.max(0, count - range.lowerBound))
    }
}
extension Substring {
    var string: String { return String(self) }
}    

extension BidirectionalCollection {
    subscript(safe offset: Int) -> Element? {
        guard !isEmpty, let i = index(startIndex, offsetBy: offset, limitedBy: index(before: endIndex)) else { return nil }
        return self[i]
    }
}

тестирование

let test = "Hello USA !!! Hello Brazil !!!"
test[safe: 10]   // ""
test[11]   // "!"
test[10...]   // "!!! Hello Brazil !!!"
test[10..<12]   // "!"
test[10...12]   // "!!"
test[...10]   // "Hello USA "
test[..<10]   // "Hello USA "
test.first   // "H"
test.last    // "!"

// Subscripting the Substring
 test[...][...3]  // "Hell"

// Note that they all return a Substring of the original String.
// To create a new String you need to add .string as follow
test[10...].string  // "!!! Hello Brazil !!!"
  • 0
    Могу я спросить, что такое «self [index (startIndex, offsetBy: i)]»? И как работает «я [я]»?
  • 1
    Привет, Лео, спасибо за решение! Я только что (сегодня) переключился с Swift 2.3 на 3, и ваш индекс решения (диапазон: Range <Int>) выдает ошибку «Дополнительный аргумент« limitedBy »в вызове». Что вы думаете может быть не так?
Показать ещё 5 комментариев
50

Swift 4

let str = "My String"

Строка с индексом

let index = str.index(str.startIndex, offsetBy: 3)
String(str[index])    // "S"

подстроку

let startIndex = str.index(str.startIndex, offsetBy: 3)
let endIndex = str.index(str.startIndex, offsetBy: 7)
String(str[startIndex...endIndex])     // "Strin"

Первые n символов

let startIndex = str.index(str.startIndex, offsetBy: 3)
String(str[..<startIndex])    // "My "

Последние n символов

let startIndex = str.index(str.startIndex, offsetBy: 3)
String(str[startIndex...])    // "String"

Swift 2 и 3

str = "My String"

** Строка по индексу **

Swift 2

let charAtIndex = String(str[str.startIndex.advancedBy(3)])  // charAtIndex = "S"

Swift 3

str[str.index(str.startIndex, offsetBy: 3)]

SubString fromIndex toIndex

Swift 2

let subStr = str[str.startIndex.advancedBy(3)...str.startIndex.advancedBy(7)] // subStr = "Strin"

Swift 3

str[str.index(str.startIndex, offsetBy: 3)...str.index(str.startIndex, offsetBy: 7)]

Первые n символов

let first2Chars = String(str.characters.prefix(2)) // first2Chars = "My"

Последние n символов

let last3Chars = String(str.characters.suffix(3)) // last3Chars = "ing"
25

Swift 2.0 от Xcode 7 GM Seed

var text = "Hello, world!"

let firstChar = text[text.startIndex.advancedBy(0)] // "H"

Для n-го символа замените 0 на n-1.

Изменить: Swift 3.0

text[text.index(text.startIndex, offsetBy: 0)]


нотабене есть более простые способы захвата определенных символов в строке

например. let firstChar = text.characters.first

23

Если вы видите Cannot subscript a value of type 'String'..., используйте это расширение:

Swift 3

extension String {
    subscript (i: Int) -> Character {
        return self[self.characters.index(self.startIndex, offsetBy: i)]
    }

    subscript (i: Int) -> String {
        return String(self[i] as Character)
    }

    subscript (r: Range<Int>) -> String {
        let start = index(startIndex, offsetBy: r.lowerBound)
        let end = index(startIndex, offsetBy: r.upperBound)
        return self[start..<end]
    }

    subscript (r: ClosedRange<Int>) -> String {
        let start = index(startIndex, offsetBy: r.lowerBound)
        let end = index(startIndex, offsetBy: r.upperBound)
        return self[start...end]
    }
}

Swift 2.3

extension String {
    subscript(integerIndex: Int) -> Character {
        let index = advance(startIndex, integerIndex)
        return self[index]
    }

    subscript(integerRange: Range<Int>) -> String {
        let start = advance(startIndex, integerRange.startIndex)
        let end = advance(startIndex, integerRange.endIndex)
        let range = start..<end
        return self[range]
    }
}

Источник: http://oleb.net/blog/2014/07/swift-strings/

19

Swift 2.2 Решение:

Следующее расширение работает в Xcode 7, это комбинация это решение и синтаксическое преобразование Swift 2.0.

extension String {
    subscript(integerIndex: Int) -> Character {
        let index = startIndex.advancedBy(integerIndex)
        return self[index]
    }

    subscript(integerRange: Range<Int>) -> String {
        let start = startIndex.advancedBy(integerRange.startIndex)
        let end = startIndex.advancedBy(integerRange.endIndex)
        let range = start..<end
        return self[range]
    }
}
11

В качестве примечания, есть несколько функций, применимых непосредственно к представлению символьной цепочки String, например:

var string = "Hello, playground"
let firstCharacter = string.characters.first // returns "H"
let lastCharacter = string.characters.last // returns "d"

Результат имеет тип Character, но вы можете передать его в String.

Или это:

let reversedString = String(string.characters.reverse())
// returns "dnuorgyalp ,olleH" 

: -)

11

Класс быстрых строк не предоставляет возможность получить символ по определенному индексу из-за его собственной поддержки символов UTF. Переменная длина символа UTF в памяти делает невозможным переключение непосредственно на символ. Это означает, что вам приходится вручную перематывать строку по каждой строке.

Вы можете расширить String, чтобы предоставить метод, который будет проходить через символы до нужного индекса

extension String {
    func characterAtIndex(index: Int) -> Character? {
        var cur = 0
        for char in self {
            if cur == index {
                return char
            }
            cur++
        }
        return nil
    }
}

myString.characterAtIndex(0)!
  • 3
    Вы уже можете перебирать строки: для буквы в "foo" {println (letter)}
  • 0
    @Doobeh Я имел в виду цикл через и вернуть реальный символ, как в моем редактировании выше
Показать ещё 6 комментариев
10

Swift 4

String(Array(stringToIndex)[index]) 

Это, наверное, лучший способ решить эту проблему разом. Вероятно, вы хотите сначала перенести строку как массив, а затем снова передать результат в виде строки. В противном случае символ будет возвращен вместо строки.

Пример String(Array("HelloThere")[1]) вернет "e" в виде строки.

(Array("HelloThere")[1] вернет символ "e" в качестве символа.

Swift не позволяет индексировать строки как массивы, но это выполняет задание, стиль грубой силы.

5

У меня была такая же проблема. Просто сделайте следующее:

var aString: String = "test"
var aChar:unichar = (aString as NSString).characterAtIndex(0)
  • 0
    Это не работает для многих эмодзи и других персонажей, которые на самом деле занимают более одного раза «персонаж» в NSString .
3

Мое очень простое решение:

let myString = "Test string"
let index = 0
let firstCharacter = myString[String.Index(encodedOffset: index)]
  • 0
    Работает в Swift 4.1
3

Мое решение находится в одной строке, предполагая, что cadena - это строка, а 4 - это n-я позиция, которую вы хотите:

let character = cadena[advance(cadena.startIndex, 4)]

Простой... Полагаю, что Swift будет включать больше вещей о подстроках в будущих версиях.

  • 1
    Разве это не то же самое, что var charAtIndex = string[advance(string.startIndex, 10)] в ответе Султана?
  • 0
    Да, это то же самое решение с другим примером, как сказал Султан. Извините за дубликат. :) Легко два человека нашли то же самое.
2

Получить и установить индекс (строка и подстрока) - Swift 4.2

Swift 4.2, Xcode 10

Я основал свой ответ на ответе @alecarlson. Единственная большая разница в том, что вы можете получить возвращенную Substring или String (а в некоторых случаях один Character). Вы также можете get и set индекс. Наконец, мой немного более громоздкий и более длинный, чем ответ @alecarlson, и поэтому я предлагаю вам поместить его в исходный файл.


Расширение:

public extension String {
    public subscript (i: Int) -> Character {
        get {
            return self[index(startIndex, offsetBy: i)]
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
    public subscript (bounds: CountableRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ..< end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeThrough<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ... end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ..< end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }

    public subscript (i: Int) -> String {
        get {
            return "\(self[index(startIndex, offsetBy: i)])"
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            self.replaceSubrange(n...n, with: "\(c)")
        }
    }
    public subscript (bounds: CountableRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ..< end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeThrough<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ... end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ..< end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }

    public subscript (i: Int) -> Substring {
        get {
            return Substring("\(self[index(startIndex, offsetBy: i)])")
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
}
public extension Substring {
    public subscript (i: Int) -> Character {
        get {
            return self[index(startIndex, offsetBy: i)]
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }

    }
    public subscript (bounds: CountableRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ..< end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: PartialRangeThrough<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ... end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ..< end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }
    public subscript (i: Int) -> String {
        get {
            return "\(self[index(startIndex, offsetBy: i)])"
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
    public subscript (bounds: CountableRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ..< end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeThrough<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ... end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ..< end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }

    public subscript (i: Int) -> Substring {
        get {
            return Substring("\(self[index(startIndex, offsetBy: i)])")
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
}
2

Swift 4

Диапазон и частичный диапазон индексирование с использованием String indices свойства

Как изменения @LeoDabus хорошего ответа, мы можем добавить дополнительное расширение для DefaultBidirectionalIndices с целью позволить нам падать обратно на indices собственности String при реализации пользовательских индексов (по Int специализированы диапазоны и частичные диапазоны) для последнего.

extension DefaultBidirectionalIndices {
    subscript(at: Int) -> Elements.Index {
        return index(startIndex, offsetBy: at)
    }
}

// Moving the index(_:offsetBy:) to an extension yields slightly
// briefer implementations for these String extensions.
extension String {
    subscript(r: CountableClosedRange<Int>) -> SubSequence {
        return self[indices[r.lowerBound]...indices[r.upperBound]]
    }
    subscript(r: CountablePartialRangeFrom<Int>) -> SubSequence {
        return self[indices[r.lowerBound]...]
    }
    subscript(r: PartialRangeThrough<Int>) -> SubSequence {
        return self[...indices[r.upperBound]]
    }
    subscript(r: PartialRangeUpTo<Int>) -> SubSequence {
        return self[..<indices[r.upperBound]]
    }
}

let str = "foo bar baz bax"
print(str[4...6]) // "bar"
print(str[4...])  // "bar baz bax"
print(str[...6])  // "foo bar"
print(str[..<6])  // "foo ba"

Спасибо @LeoDabus за то, что указали мне в сторону использования свойства indices в качестве (другой) альтернативы подстроке String !

Swift 4.2.

В Swift 4.2 DefaultBidirectionalIndices устарел в пользу DefaultIndices.

  • 1
    единственный недостаток - CountableClosedRange сместит оба индекса из startIndex
  • 0
    stackoverflow.com/a/38215613/4573247 теперь расширяет StringProtocol для поддержки подстрок
Показать ещё 4 комментария
2

Swift 3: другое решение (проверено на детской площадке)

extension String {
    func substr(_ start:Int, length:Int=0) -> String? {
        guard start > -1 else {
            return nil
        }

        let count = self.characters.count - 1

        guard start <= count else {
            return nil
        }

        let startOffset = max(0, start)
        let endOffset = length > 0 ? min(count, startOffset + length - 1) : count

        return self[self.index(self.startIndex, offsetBy: startOffset)...self.index(self.startIndex, offsetBy: endOffset)]
    }
}

Использование:

let txt = "12345"

txt.substr(-1) //nil
txt.substr(0) //"12345"
txt.substr(0, length: 0) //"12345"
txt.substr(1) //"2345"
txt.substr(2) //"345"
txt.substr(3) //"45"
txt.substr(4) //"5"
txt.substr(6) //nil
txt.substr(0, length: 1) //"1"
txt.substr(1, length: 1) //"2"
txt.substr(2, length: 1) //"3"
txt.substr(3, length: 1) //"4"
txt.substr(3, length: 2) //"45"
txt.substr(3, length: 3) //"45"
txt.substr(4, length: 1) //"5"
txt.substr(4, length: 2) //"5"
txt.substr(5, length: 1) //nil
txt.substr(5, length: -1) //nil
txt.substr(-1, length: -1) //nil
2

Swift3

Вы можете использовать синтаксис индекса для доступа к символу в конкретном индексе String.

let greeting = "Guten Tag!"
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index] // a

Перейдите на страницу https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html.

или мы можем сделать расширение строки в Swift 4

extension String {
    func getCharAtIndex(_ index: Int) -> Character {
        return self[self.index(self.startIndex, offsetBy: index)]
    }
}

ИСПОЛЬЗОВАНИЕ:

let foo = "ABC123"
foo.getCharAtIndex(2) //C
2

В Swift 3

    let mystring = "Hello, world!"
    let stringToArray = Array(mystring.characters)

    let indices = (stringToArray.count)-1

    print(stringToArray[0]) //H
    print(stringToArray[indices]) //!
  • 0
    mystring даже не содержит свойства, называемое символами в Swift 3
  • 0
    Хотя эти комментарии становятся немного странными, код работает. Это не полный ответ, но .characters это вещь. @Ankahathara ты проверял это на детской площадке?
Показать ещё 1 комментарий
2

Swift 3

extension String {

    public func charAt(_ i: Int) -> Character {
        return self[self.characters.index(self.startIndex, offsetBy: i)]
    }

    public subscript (i: Int) -> String {
        return String(self.charAt(i) as Character)
    }

    public subscript (r: Range<Int>) -> String {
        return substring(with: self.characters.index(self.startIndex, offsetBy: r.lowerBound)..<self.characters.index(self.startIndex, offsetBy: r.upperBound))
    }

    public subscript (r: CountableClosedRange<Int>) -> String {
        return substring(with: self.characters.index(self.startIndex, offsetBy: r.lowerBound)..<self.characters.index(self.startIndex, offsetBy: r.upperBound))
    }

}

Использование

let str = "Hello World"
let sub = str[0...4]

Полезные советы и рекомендации по программированию (написано мной)

2

Я думаю, что быстрый ответ для получения первого символа может быть:

let firstCharacter = aString[aString.startIndex]

Это так элегантно и качественно, как:

let firstCharacter = Array(aString.characters).first

Но.. если вы хотите манипулировать и делать больше операций со строками, вы можете подумать о создании расширения... там есть одно расширение с этим подходом, оно очень похоже на уже опубликованное здесь:

extension String {
var length : Int {
    return self.characters.count
}

subscript(integerIndex: Int) -> Character {
    let index = startIndex.advancedBy(integerIndex)
    return self[index]
}

subscript(integerRange: Range<Int>) -> String {
    let start = startIndex.advancedBy(integerRange.startIndex)
    let end = startIndex.advancedBy(integerRange.endIndex)
    let range = start..<end
    return self[range]
}

}

НО ЭТО ТЕРРОМНАЯ ИДЕЯ!

Расширение ниже ужасно неэффективно. Каждый раз, когда к строке обращается целое число, выполняется функция O (n) для продвижения начального индекса. Запуск линейного цикла внутри другого линейного цикла означает, что для цикла случайно O (n2) - по мере увеличения длины строки время, которое этот цикл принимает, увеличивается квадратично.

Вместо этого вы можете использовать коллекцию строк символов.

2

Обновление для swift 2.0 subString

public extension String {
    public subscript (i: Int) -> String {
        return self.substringWithRange(self.startIndex..<self.startIndex.advancedBy(i + 1))
    }

    public subscript (r: Range<Int>) -> String {
        get {
            return self.substringWithRange(self.startIndex.advancedBy(r.startIndex)..<self.startIndex.advancedBy(r.endIndex))
        }
    }

}
  • 0
    Только сработал ответ за меня. (Swift 2.3)
2

Чтобы прокормить тему и показать быстрые возможности подстроки, здесь небольшая строка подстроки подстроки

Эти методы безопасны и никогда не переходят к строковым индексам

extension String {
    // string[i] -> one string char
    subscript(pos: Int) -> String { return String(Array(self)[min(self.length-1,max(0,pos))]) }

    // string[pos,len] -> substring from pos for len chars on the left
    subscript(pos: Int, len: Int) -> String { return self[pos, len, .pos_len, .left2right] }

    // string[pos, len, .right2left] -> substring from pos for len chars on the right
    subscript(pos: Int, len: Int, way: Way) -> String { return self[pos, len, .pos_len, way] }

    // string[range] -> substring form start pos on the left to end pos on the right
    subscript(range: Range<Int>) -> String { return self[range.startIndex, range.endIndex, .start_end, .left2right] }

    // string[range, .right2left] -> substring start pos on the right to end pos on the left
    subscript(range: Range<Int>, way: Way) -> String { return self[range.startIndex, range.endIndex, .start_end, way] }

    var length: Int { return countElements(self) }
    enum Mode { case pos_len, start_end }
    enum Way { case left2right, right2left }
    subscript(var val1: Int, var val2: Int, mode: Mode, way: Way) -> String {
        if mode == .start_end {
            if val1 > val2 { let val=val1 ; val1=val2 ; val2=val }
            val2 = val2-val1
        }
        if way == .left2right {
            val1 = min(self.length-1, max(0,val1))
            val2 = min(self.length-val1, max(1,val2))
        } else {
            let val1_ = val1
            val1 = min(self.length-1, max(0, self.length-val1_-val2 ))
            val2 = max(1, (self.length-1-val1_)-(val1-1) )
        }
        return self.bridgeToObjectiveC().substringWithRange(NSMakeRange(val1, val2))

        //-- Alternative code without bridge --
        //var range: Range<Int> = pos...(pos+len-1)
        //var start = advance(startIndex, range.startIndex)
        //var end = advance(startIndex, range.endIndex)
        //return self.substringWithRange(Range(start: start, end: end))
    }
}


println("0123456789"[3]) // return "3"

println("0123456789"[3,2]) // return "34"

println("0123456789"[3,2,.right2left]) // return "56"

println("0123456789"[5,10,.pos_len,.left2right]) // return "56789"

println("0123456789"[8,120,.pos_len,.right2left]) // return "01"

println("0123456789"[120,120,.pos_len,.left2right]) // return "9"

println("0123456789"[0...4]) // return "01234"

println("0123456789"[0..4]) // return "0123"

println("0123456789"[0...4,.right2left]) // return "56789"

println("0123456789"[4...0,.right2left]) // return "678" << because ??? range can wear endIndex at 0 ???
1

Есть альтернатива, поясняемая в Строковый манифест

extension String : BidirectionalCollection {
    subscript(i: Index) -> Character { return characters[i] }
}
1

Использование символов выполнит задание. Вы можете быстро преобразовать String в массив символов, которые могут обрабатываться методами CharacterView.

Пример:

let myString = "Hello World!"
let myChars  = myString.characters

(полный CharacterView doc)

(проверено в Swift 3)

1

В Swift 3 без расширений класса String так просто, как я могу это сделать!

let myString = "abcedfg"
let characterLocationIndex = myString.index(myString.startIndex, offsetBy: 3)
let myCharacter = myString[characterLocationIndex]

myCharacter - это "3" в этом примере.

1

Вы также можете преобразовать String в массив таких символов:

let text = "My Text"
let index = 2
let charSequence = text.unicodeScalars.map{ Character($0) }
let char = charSequence[index]

Это способ получить char по указанному индексу в постоянное время.

Приведенный ниже пример не выполняется в постоянное время, но требует линейного времени. Итак, если у вас много поиска в String по индексу, используйте метод выше.

let char = text[text.startIndex.advancedBy(index)]
1

Питон-подобное решение, которое позволяет использовать отрицательный индекс,

var str = "Hello world!"
str[-1]        // "!"

может быть:

extension String {
    subscript (var index:Int)->Character{
        get {
            let n = distance(self.startIndex, self.endIndex)
            index %= n
            if index < 0 { index += n }
            return self[advance(startIndex, index)]
        }
    }
}

Кстати, это может стоить транспонировать целую ноту фрагмента питона

  • 0
    Вы не возражаете написать что-то, что компилируется для Swift 4? последнее возвращение ... строка, похоже, не работает.
1

Получите первое письмо:

first(str) // retrieve first letter

Подробнее здесь: http://sketchytech.blogspot.com/2014/08/swift-pure-swift-method-for-returning.html

1

Тип Swift String не предоставляет метод characterAtIndex, поскольку существует несколько способов кодирования строки Unicode. Вы собираетесь с UTF8, UTF16 или еще чем-нибудь?

Вы можете получить доступ к коллекциям CodeUnit, получив свойства String.utf8 и String.utf16. Вы также можете получить доступ к коллекции UnicodeScalar, получив свойство String.unicodeScalars.

В духе реализации NSString я возвращаю тип unichar.

extension String
{
    func characterAtIndex(index:Int) -> unichar
    {
        return self.utf16[index]
    }

    // Allows us to use String[index] notation
    subscript(index:Int) -> unichar
    {
        return characterAtIndex(index)
    }
}

let text = "Hello Swift!"
let firstChar = text[0]
  • 0
    Это не удастся для символов, которые требуют больше памяти, чем 16 бит. По сути, любой символ Unicode, кроме U + FFFF.
0

Swift 4.2

Этот ответ является идеальным, поскольку он расширяет String и все его Subsequences (Substring) в одном расширении

public extension StringProtocol {

    public subscript (i: Int) -> Element {
        return self[index(startIndex, offsetBy: i)]
    }

    public subscript (bounds: CountableClosedRange<Int>) -> SubSequence {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[start...end]
    }

    public subscript (bounds: CountableRange<Int>) -> SubSequence {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[start..<end]
    }

    public subscript (bounds: PartialRangeUpTo<Int>) -> SubSequence {
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[startIndex..<end]
    }

    public subscript (bounds: PartialRangeThrough<Int>) -> SubSequence {
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[startIndex...end]
    }

    public subscript (bounds: CountablePartialRangeFrom<Int>) -> SubSequence {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        return self[start..<endIndex]
    }
}

использование

var str = "Hello, playground"

print(str[5...][...5][0])
// Prints ","
  • 0
    Это излишне смещает оба индекса ( start и end ) из startIndex . Вы можете просто сместить end индекс с помощью range.count и сместить start индекс
0

Включите это расширение в свой проект

  extension String{
func trim() -> String
{
    return self.trimmingCharacters(in: NSCharacterSet.whitespaces)
}

var length: Int {
    return self.count
}

subscript (i: Int) -> String {
    return self[i ..< i + 1]
}

func substring(fromIndex: Int) -> String {
    return self[min(fromIndex, length) ..< length]
}

func substring(toIndex: Int) -> String {
    return self[0 ..< max(0, toIndex)]
}

subscript (r: Range<Int>) -> String {
    let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)),
                                        upper: min(length, max(0, r.upperBound))))
    let start = index(startIndex, offsetBy: range.lowerBound)
    let end = index(start, offsetBy: range.upperBound - range.lowerBound)
    return String(self[start ..< end])
}

func substring(fromIndex: Int, toIndex:Int)->String{
    let startIndex = self.index(self.startIndex, offsetBy: fromIndex)
    let endIndex = self.index(startIndex, offsetBy: toIndex-fromIndex)

    return String(self[startIndex...endIndex])
}

Затем используйте функцию, подобную этой

let str = "Sample-String"

let substring = str.substring(fromIndex: 0, toIndex: 0) //returns S
let sampleSubstr = str.substring(fromIndex: 0, toIndex: 5) //returns Sample
0

Swift 3:

extension String {
    func substring(fromPosition: UInt, toPosition: UInt) -> String? {
        guard fromPosition <= toPosition else {
            return nil
        }

        guard toPosition < UInt(characters.count) else {
            return nil
        }

        let start = index(startIndex, offsetBy: String.IndexDistance(fromPosition))
        let end   = index(startIndex, offsetBy: String.IndexDistance(toPosition) + 1)
        let range = start..<end

        return substring(with: range)
    }
}

"ffaabbcc".substring(fromPosition: 2, toPosition: 5) // return "aabb"
0

Здесь вы можете использовать расширение, работая с Swift 3.1. Один индекс возвращает Character, который кажется интуитивным при индексировании a String, а a Range возвращает String.

extension String {
    subscript (i: Int) -> Character {
        return Array(self.characters)[i]
    }

    subscript (r: CountableClosedRange<Int>) -> String {
        return String(Array(self.characters)[r])
    }

    subscript (r: CountableRange<Int>) -> String {
        return self[r.lowerBound...r.upperBound-1]
    }
}

Некоторые примеры расширения в действии:

let string = "Hello"

let c1 = string[1]  // Character "e"
let c2 = string[-1] // fatal error: Index out of range

let r1 = string[1..<4] // String "ell"
let r2 = string[1...4] // String "ello"
let r3 = string[1...5] // fatal error: Array index is out of range



n.b. Вы можете добавить дополнительный метод к вышеуказанному расширению, чтобы вернуть String с единственным символом, если захотите:

subscript (i: Int) -> String {
    return String(self[i])
}

Обратите внимание, что тогда вам нужно будет явно указать тип, который вам нужен, при индексировании строки:

let c: Character = string[3] // Character "l"
let s: String = string[0]    // String "H"
0

Разрешает отрицательные индексы

Всегда полезно не всегда писать string[string.length - 1], чтобы получить последний символ при использовании расширения подстроки. Это расширение (Swift 3) позволяет использовать отрицательные индексы, Range и CountableClosedRange.

extension String {
    var count: Int { return self.characters.count }

    subscript (i: Int) -> Character {
        // wraps out of bounds indices
        let j = i % self.count
        // wraps negative indices
        let x = j < 0 ? j + self.count : j

        // quick exit for first
        guard x != 0 else {
            return self.characters.first!
        }

        // quick exit for last
        guard x != count - 1 else {
            return self.characters.last!
        }

        return self[self.index(self.startIndex, offsetBy: x)]
    }

    subscript (r: Range<Int>) -> String {
        let lb = r.lowerBound
        let ub = r.upperBound

        // quick exit for one character
        guard lb != ub else { return String(self[lb]) }

        return self[self.index(self.startIndex, offsetBy: lb)..<self.index(self.startIndex, offsetBy: ub)]
    }

    subscript (r: CountableClosedRange<Int>) -> String {
        return self[r.lowerBound..<r.upperBound + 1]
    }
}

Как вы можете его использовать:

var text = "Hello World"

text[-1]    // d
text[2]     // l
text[12]    // e
text[0...4] // Hello
text[0..<4] // Hell

Для более тщательного программиста: включите guard в пустые строки в этом расширении

subscript (i: Int) -> Character {
    guard self.count != 0 else { return '' }
    ...
}

subscript (r: Range<Int>) -> String {
    guard self.count != 0 else { return "" }
    ...
}
0

Я хотел указать, что если у вас есть большая строка и вам нужно случайно получить доступ ко многим символам, вы можете заплатить дополнительную стоимость памяти и преобразовать строку в массив для лучшей производительности:

// Pay up front for O(N) memory
let chars = Array(veryLargeString.characters)

for i in 0...veryLargeNumber {
    // Benefit from O(1) access
    print(chars[i])
}
0

Для этого вы можете использовать SwiftString (https://github.com/amayne/SwiftString).

"Hello, world!"[0] // H
"Hello, world!"[0...4] // Hello

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я написал это расширение

Ещё вопросы

Сообщество Overcoder
Наверх
Меню