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


419

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

var string = "Hello, world!"

var firstChar = string[0] // Throws error

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


1
Сообщение об ошибке «не может подписать строку с помощью Int, см. Комментарий к документации для обсуждения», похоже, ссылается на github.com/apple/swift/blob/master/stdlib/public/core/…
andrewdotn

используйте var firstChar = string.index(string.startIndex, offsetBy: 0)вместо этого
Sazzad Hissain Khan

@SazzadHissainKhan это приведет к строковому индексу, а не к символу. Кстати, почему не просто string.startIndex? Для первого персонажа string[string.startIndex] или просто string.first. Обратите внимание, что при первом подходе вам необходимо проверить, пуста ли строка, во-первых, второй возвращает необязательный
Leo

Ответы:


567

Внимание: пожалуйста, смотрите ответ Лео Дабуса для правильной реализации Swift 4 и Swift 5.

Swift 4 или позже

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

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

extension StringProtocol {
    subscript(offset: Int) -> Character { self[index(startIndex, offsetBy: offset)] }
    subscript(range: Range<Int>) -> SubSequence {
        let startIndex = index(self.startIndex, offsetBy: range.lowerBound)
        return self[startIndex..<index(startIndex, offsetBy: range.count)]
    }
    subscript(range: ClosedRange<Int>) -> SubSequence {
        let startIndex = index(self.startIndex, offsetBy: range.lowerBound)
        return self[startIndex..<index(startIndex, offsetBy: range.count)]
    }
    subscript(range: PartialRangeFrom<Int>) -> SubSequence { self[index(startIndex, offsetBy: range.lowerBound)...] }
    subscript(range: PartialRangeThrough<Int>) -> SubSequence { self[...index(startIndex, offsetBy: range.upperBound)] }
    subscript(range: PartialRangeUpTo<Int>) -> SubSequence { self[..<index(startIndex, offsetBy: range.upperBound)] }
}

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

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


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

Сообщение об ошибке гласит «см. Комментарий к документации для обсуждения» . Apple предоставляет следующее объяснение в файле UnavailableStringAPIs.swift :

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

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

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

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

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

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

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

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


4
Cannot find an overload for 'advance' that accepts an argument list of type '(String.Index, T)'... String.Indexи Intне совместимы

7
Если вы видите, Cannot subscript a value of type 'String'...проверьте этот ответ: stackoverflow.com/a/31265316/649379
SoftDesigner

24
Когда я пытаюсь использовать это, я получаю Ambiguous use of 'subscript'.
Джоуи

18
ПРЕДУПРЕЖДЕНИЕ! Расширение ниже ужасно неэффективно. Каждый раз, когда к строке обращаются с помощью целого числа, запускается функция O (n) для продвижения ее начального индекса. Запуск линейного цикла внутри другого линейного цикла означает, что этот цикл for случайно равен O (n2) - при увеличении длины строки время, которое занимает этот цикл, увеличивается квадратично. Вместо этого вы можете использовать коллекцию строк символов.
ignaciohugog

2
fatal error: Can't form a Character from an empty String
Девин Б

341

Swift 5.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 всегда было готовое решение этой проблемы (без расширения String, которое я предоставил ниже), я все равно настоятельно рекомендую использовать расширение. Почему? Потому что это сэкономило мне десятки часов мучительного перехода от ранних версий Swift, где синтаксис String менялся почти в каждом выпуске, но все, что мне нужно было сделать, это обновить реализацию расширения, а не рефакторинг всего проекта. Сделай свой выбор.

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"

изменить range.upperBound - range.lowerBoundнаrange.count
Лев

Это не часть первоначального вопроса, но ... было бы неплохо, если бы это поддерживало назначение. Например, s [i] = "a" :).
Крис Принс

2
Я считаю, что с Swift 4.2 подписки снова недоступны. Я получаю сообщение об ошибке: «подписка» недоступна: не могу подписать строку с помощью Int, см. Комментарий к документации для обсуждения
C0D3

2
@ChrisPrinceextension StringProtocol where Self: RangeReplaceableCollection { subscript(offset: Int) -> Element { get { return self[index(startIndex, offsetBy: offset)] } set { let start = index(startIndex, offsetBy: offset) replaceSubrange(start..<index(after: start), with: [newValue]) } } }
Лео Дабус

Это должны быть встроенные функции
Аммар Муджиб

150

Я только что придумал этот аккуратный обходной путь

var firstChar = Array(string)[0]

2
Это хороший быстрый обходной путь для (распространенного) случая, когда вы знаете, что у вас есть строки в кодировке UTF8 или ASCII. Просто убедитесь, что строки никогда не будут в кодировке, которая использует более одного байта.
Джефф Хей

48
Это кажется крайне неэффективным, поскольку вы копируете всю строку только для того, чтобы получить первый символ. Используйте строку [строка. startIndex] вместо 0, как указал Султан.
Бьорн

6
Разверните строку: в var firstChar = Array(string!)[0]противном случае он скажетadd arrayLiteral
Мухаммед Заид Патан

2
Я не считаю, что это чисто, на самом деле это кругом. Я не совсем уверен, какой инициализатор в Array используется первым, что вызывает это (и я предполагаю, что это инициализатор SequenceType, заставляющий его собирать символы строки как отдельные компоненты для Array). Это совсем не явно и может быть исправлено в будущем без приведения типов. Это также не работает, если вы используете сокращение для массива через [string] .first. Решение @ Sulthan лучше всего использует запеченные в значениях индекса. Намного яснее, что здесь происходит.
TheCodingArt

2
Вау, ошибка сегмента компилятора!
Франк

124

Нет индексации с использованием целых чисел, только с помощью 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индексы уникальны для строки. Это связано с тем, что разные строки могут иметь разные UTF-16 с несколькими единицами Charactersи / или в разных позициях, поэтому индексы единиц UTF-16 не будут совпадать, могут выходить за пределы конца или точки внутри UTF-16 с несколькими единицами Character.
zaph

@Zaph Это очевидно.
Султан

3
Объясняя, почему вы говорите: «иногда это приводит к сбою или приводит к неопределенному поведению». Возможно, лучше сказать, просто не делай этого, потому что ...
zaph

1
@Sulthan ..сейчас ..<(в вашем назначении range)
Аарон Брагер

3
@CajunLuke Я знаю, что прошло много времени с тех пор, как вы оставили этот комментарий, но взгляните на этот ответ . Вы можете использоватьvar lastChar = string[string.endIndex.predecessor()]
Дэвид L

122

Xcode 11 • Swift 5.1

Вы можете расширить StringProtocol, чтобы сделать индекс доступным также для подстрок:

extension StringProtocol {
    subscript(_ offset: Int)                     -> Element     { self[index(startIndex, offsetBy: offset)] }
    subscript(_ range: Range<Int>)               -> SubSequence { prefix(range.lowerBound+range.count).suffix(range.count) }
    subscript(_ range: ClosedRange<Int>)         -> SubSequence { prefix(range.lowerBound+range.count).suffix(range.count) }
    subscript(_ range: PartialRangeThrough<Int>) -> SubSequence { prefix(range.upperBound.advanced(by: 1)) }
    subscript(_ range: PartialRangeUpTo<Int>)    -> SubSequence { prefix(range.upperBound) }
    subscript(_ range: PartialRangeFrom<Int>)    -> SubSequence { suffix(Swift.max(0, count-range.lowerBound)) }
}

extension LosslessStringConvertible {
    var string: String { .init(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 from a substring
test[10...].string  // "🇺🇸!!! Hello Brazil 🇧🇷!!!"

Могу я спросить, что такое «self [index (startIndex, offsetBy: i)]»? И как работает «я [я]»?
Алленлинли

1
Привет, Лео, спасибо за решение! Я только что (сегодня) переключился с Swift 2.3 на 3, и ваш индекс решения (диапазон: Range <Int>) выдает ошибку «Дополнительный аргумент« limitedBy »в вызове». Что вы думаете может быть не так?
Ахмет Аккёк

@ АхметАккёк, ты уверен, что не менял код?
Лео Дабус

1
@ Оказалось, что я не конвертировал весь проект, но в приложении, а не в расширении, я повторил процесс как для приложения, так и для расширения, и теперь он работает нормально. Ваша помощь очень ценится!
Ахмет Аккёк

это очень сложный код. В чем преимущество перед return String(Array(characters)[range])Swift 3?
Дэн Розенстарк

69

Swift 4

let str = "My String"

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

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

Substring

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"

Свифт 2 и 3

str = "My String"

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

Swift 2

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

Свифт 3

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

Подстрока из индекса в индекс

Swift 2

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

Свифт 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"

24

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)]


NB Есть более простые способы получения определенных символов в строке

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


23

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

Свифт 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]
    }
}

14

Класс быстрой строки не предоставляет возможности получить символ по определенному индексу из-за его собственной поддержки символов 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)}
Doobeh

@Doobeh Я имел в виду цикл и вернуть реальный символ, как в моем редактировании выше
drewag

отлично! Забавно, как вы можете проходить через это, но не через индекс. Свифт чувствует себя питоничным, но с более жесткими краями.
Doobeh

Я нашел, используя myString.bridgeToObjectiveC().characterAtIndex(0)или (string as NSString ).characterAtIndex(0) возвращает значение Int для символа
markhunte

4
Не используйте NSStringметоды для доступа к отдельным символам из Swift-native String- оба используют разные механизмы подсчета, поэтому вы получите непредсказуемые результаты с более высокими символами Unicode. Первый метод должен быть безопасным (после обработки ошибок Юникода в Swift).
Нейт Кук

11

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

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

Swift 4

String(Array(stringToIndex)[index]) 

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

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

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

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


1
Дублирование всего содержимого строки в другую ячейку памяти неэффективно, особенно для больших строк. Нам не нужны дополнительные выделения памяти для простых задач, таких как прямой доступ к памяти.
Cristik

7

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

Swift 4.1:

let myString = "Test string"
let index = 0
let firstCharacter = myString[String.Index(encodedOffset: index)]

Swift 5.1:

let firstCharacter = myString[String.Index.init(utf16Offset: index, in: myString)]

Работает в Swift 4.1
leanne


@ Линь Дао Не используйте encodedOffset. encodedOffset устарел: encodedOffset устарел, так как наиболее распространенное использование является неправильным.
Лев Дабус

@OhadM самое простое не означает, что это правильно или, по крайней мере, оно не будет работать так, как вы ожидаете. Попробуйтеlet flags = "🇺🇸🇧🇷" flags[String.Index(utf16Offset: 4, in: flags)] // "🇧🇷"
Лео Дабус

1
@OhadM Мой комментарий был просто предупреждением. Не стесняйтесь использовать его, если вы думаете, что он ведет себя так, как вы ожидаете.
Лев Дабус

6

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

var str = "Hello"
let s = Array(str)[2]
print(s)

1
Обратите внимание, что это решение приведет к дублированию содержимого, что сделает его менее производительным в отношении памяти и ЦП.
Cristik

5

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

var aString: String = "test"
var aChar:unichar = (aString as NSString).characterAtIndex(0)

Это не работает для многих эмодзи и других персонажей, которые на самом деле занимают более одного «персонажа» в NSString.
rmaddy

5

Swift3

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

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

3

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

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

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


1
Разве это не то же самое, что var charAtIndex = string[advance(string.startIndex, 10)]в ответе Султана?
Мартин Р

Да, это то же самое решение с другим примером, как сказал Султан. Извините за дубликат. :) Легко два человека нашли одинаковый путь.
Хулио Сесар Фернандес Муньос

3

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

Обновление для подстроки swift 2.0

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

}

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) для продвижения ее начального индекса. Запуск линейного цикла внутри другого линейного цикла означает, что этот цикл for случайно равен O (n2) - при увеличении длины строки время, которое занимает этот цикл, увеличивается квадратично.

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


2

Свифт 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

Получить и установить индекс (строка и подстрока) - 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)")
        }
    }
}

Это излишне смещает оба индекса (начало и конец) из startIndex. Вы можете просто сместить конечный индекс с помощью range.count и сместить начальный индекс
Лео Дабус

2

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 ","

Это излишне смещает оба индекса ( startи end) из startIndex. Вы можете просто endсместить индекс с помощью range.count и сместить startиндекс
Лео Дабус

2

В настоящее время индекс (_ :) недоступен. Как и мы не можем сделать это

str[0] 

со строкой. Мы должны предоставить «String.Index». Но как мы можем таким образом указать свой собственный индекс, вместо этого мы можем использовать

string[str.index(str.startIndex, offsetBy: 0)]

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

Зачем делать ненужные смещения? Почему не просто string[string.startIndex]? Кстати, код будет некорректно вести себя / компилироваться, так как вы использовали два разных имени переменных.
Cristik

2

Swift 4.2 или позже

Диапазон и частичный диапазон индексирование , используя String«s indicesсвойство

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

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

// Moving the index(_:offsetBy:) to an extension yields slightly
// briefer implementations for these String extensions.
extension String {
    subscript(range: Range<Int>) -> SubSequence {
        let start = indices[range.lowerBound]
        return self[start..<indices[start...][range.count]]
    }
    subscript(range: ClosedRange<Int>) -> SubSequence {
        let start = indices[range.lowerBound]
        return self[start...indices[start...][range.count]]
    }
    subscript(range: PartialRangeFrom<Int>) -> SubSequence {
        self[indices[range.lowerBound]...]
    }
    subscript(range: PartialRangeThrough<Int>) -> SubSequence {
        self[...indices[range.upperBound]]
    }
    subscript(range: PartialRangeUpTo<Int>) -> SubSequence {
        self[..<indices[range.upperBound]]
    }
}

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

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


1
единственным недостатком является то, что CountableClosedRange сместит оба индекса из startIndex
Лео Дабус

1
@ LeoDabus я вижу. Да, в основном Linux, но не сильно Swift в эти дни: / Я использую, swiftenvкогда я делаю, хотя, я думаю, он будет обновлен с 4.2 также несколько позже.
dfri

1
@LeoDabus спасибо за обновление этого ответа на современный Swift!
dfri

1
@LeoDabus хорошая работа! Придется разобраться в деталях позже, но я вспоминаю, что мне никогда не нравилось, что нам пришлось прибегнуть к фундаменту некоторых типов упорядоченных / исчисляемых множеств.
dfri

1
Спасибо, приятель!!!
Лев Дабус

2

Swift 5.1.3:

Добавьте расширение String:

extension String {

 func stringAt(_ i: Int) -> String { 
   return String(Array(self)[i]) 
 } 

 func charAt(_ i: Int) -> Character { 
  return Array(self)[i] 
 } 
}

let str = "Teja Kumar"
let str1: String = str.stringAt(2)  //"j"
let str2: Character = str.charAt(5)  //"k"

1
Это преобразует всю строку в массив символов каждый раз, когда вы вызываете это свойство, чтобы извлечь из него один символ.
Лев Дабус

1

StringТип Swift не обеспечивает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]

Это не удастся для символов, которые требуют больше памяти, чем 16 бит. По сути, любой символ Unicode, кроме U + FFFF.
rmaddy

1

Для того, чтобы подать тему и показать возможности быстрого индекса, вот небольшая строка «substring-toolbox» на основе индекса

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

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

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

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

Кстати, может быть, стоит перенести нотацию всего питона


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

1

Вы также можете конвертировать строку в массив символов, например:

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

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

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

let char = text[text.startIndex.advancedBy(index)]
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.