Отримайте n-й символ рядка в мові програмування Swift


419

Як я можу отримати n-й символ рядка? Я спробував bracket ( []) аксесуар не пощастило.

var string = "Hello, world!"

var firstChar = string[0] // Throws error

ПОМИЛКА: 'Підписка' недоступна: не може підписати String з 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 це призведе до рядкового індексу, а не до символу. Btw чому не просто string.startIndex? Для першого персонажа string[string.startIndex] або просто string.first. Зауважте, що перший підхід вам потрібно буде перевірити, чи рядок порожній перший, другий повертає необов’язково
Лео Дабус

Відповіді:


567

Увага: Будь ласка, дивіться відповідь Лео Дабуса щодо правильної реалізації для Swift 4 та Swift 5.

Swift 4 або пізнішої версії

The 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в а 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'.
jowie

18
УВАГА! Розширення внизу жахливо неефективне. Кожного разу, коли доступ до рядка з цілим числом, запускається функція O (n) для просування свого початкового індексу. Запуск лінійної петлі всередині іншого лінійного циклу означає, що це для циклу випадково O (n2) - при збільшенні довжини рядка час цього циклу збільшується квадратично. Замість цього ви могли використовувати колекцію рядків символів.
ignaciohugog

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

341

Швидкий 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 знову не доступні. Я отримую помилку: "Підписка" недоступна: не можна підписати String з 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]) } } }
Лев Дабус

Це повинні бути вбудовані функції
Ammar Mujeeb

150

Я щойно придумав це акуратне рішення

var firstChar = Array(string)[0]

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

47
Це здається надзвичайно неефективним, оскільки ви копіюєте весь рядок лише для отримання першого символу. Використовуйте рядок [рядок. startIndex] замість 0, як вказував Султан.
Бьорн

6
Розмотайте рядок: var firstChar = Array(string!)[0]інакше буде сказаноadd arrayLiteral
Мохаммед Заїд Патхан

2
Я не вірю, що це чисто, адже насправді це тур. Я не зовсім впевнений, який ініціалізатор в Array використовується вперше, викликаючи це (і я припускаю, що ініціалізатор SequenceType змушує його збирати символи рядка як окремі компоненти для масиву). Це взагалі не є явним і може бути виправлене в майбутньому без кастингу типів. Це також не працює, якщо ви використовуєте стенограму для масиву через [string] .first. @ Рішення Султана найкраще використовувати запечене в значеннях індексу. Набагато більш зрозуміло, що відбувається тут.
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]

Швидкий 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()]
David L

122

Xcode 11 • Швидкий 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)]"? І як працює "я [i]"?
allenlinli

1
Привіт Лео, дякую за рішення! Я щойно (сьогодні) перейшов з Swift 2.3 до 3, і ваш індексний індекс (діапазон: Діапазон <Int>) дає помилку "Додатковий аргумент" обмежений "у виклику". Як ви думаєте, що може бути не так?
Ахмет Акьок

@ AhmetAkkök Ви впевнені, що не змінили код?
Лев Дабус

1
@Leo виявилося, що я не перетворив весь проект, але на додаток, а не на розширення, я повторив процес і для програми, і для розширення, і він працює нормально зараз. Ваша допомога дуже вдячна!
Ахмет Акьок

це дуже складний код. Яка перевага перед тим, як робити return String(Array(characters)[range])у Swift 3?
Дан Розенстарк

68

Швидкий 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"

Швидкий 2 і 3

str = "My String"

** Рядок в індексі **

Швидкий 2

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

Швидкий 3

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

SubString відIndex доIndex

Швидкий 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

Швидкий 2.0 з насіння Xcode 7 GM

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

Швидкий 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 у пам'яті робить перехід безпосередньо до символу неможливим. Це означає, що вам доведеться щоразу переводити цикл на рядок вручну.

Ви можете розширити рядок, щоб надати метод, який буде перебирати символи до потрібного індексу

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 Я мав на увазі переосмислити та повернути фактичного персонажа, як у моїй
редакції

приємно! Смішно, як ви можете перебирати через нього, але не через індекс. Свіфт відчуває себе пітонічним, але з більш твердими краями.
Doobeh

Я знайшов, використовуючи myString.bridgeToObjectiveC().characterAtIndex(0)або (string as NSString ).characterAtIndex(0) повертає значення Int символу
markhunte

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

11

В якості відмітки, є кілька функцій, застосовних безпосередньо до ланцюжкового представлення рядка, наприклад:

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

Результат має характер символів, але ви можете передавати його до рядка.

Або це:

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

:-)


11

Швидкий 4

String(Array(stringToIndex)[index]) 

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

Приклад String(Array("HelloThere")[1])поверне "e" у вигляді рядка.

(Array("HelloThere")[1] поверне "e" як персонаж.

Swift не дозволяє Strings індексуватися, як масиви, але це виконує роботу в стилі грубої сили.


1
Дублювання всього вмісту рядка в інше місце пам'яті є протидіючим, особливо для великих рядків. Нам не потрібно мати додаткового розподілу пам'яті для простих завдань, таких як прямий доступ до пам'яті.
Крістік

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

Найпростіше рішення та зараз із прикладом Swift 5 :)
OhadM

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

@OhadM найпростіший не означає, що він правильний або принаймні не буде працювати, як ви очікували. Спробуйтеlet flags = "🇺🇸🇧🇷" flags[String.Index(utf16Offset: 4, in: flags)] // "🇧🇷"
Лев Дабус

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

6

Ви можете зробити це, перетворивши String в масив і отримати його за певним індексом, використовуючи індекс, як показано нижче

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

1
Зауважте, що це рішення призведе до дублювання вмісту, що зробить його менш продуктивним у пам’яті та на процесорі.
Крістік

5

У мене просто було те саме питання. Просто зробіть це:

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

Це не вдається для багатьох Емоджі та інших персонажів, які насправді не один раз займають "персонаж" у NSString.
rmaddy

5

Швидкий3

Ви можете використовувати синтаксис підпису для доступу до символу в певному індексі 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

або ми можемо зробити String Extension у 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

Моє рішення в одному рядку, припустимо, що cadena - це рядок, а 4 - це п яте місце, яке ви хочете:

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

Оновлення для швидкої 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))
        }
    }

}

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

Швидкий 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

Get & Set Subscript (Рядок і підрядка) - Swift 4.2

Швидкий 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

Швидкий 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індекс за допомогою діапазону.count та змістити startіндекс
Лео Дабус

2

На сьогоднішній день підпис (_ :) недоступний. Ми також не можемо цього зробити

str[0] 

з string.Ми повинні надати "String.Index". Але, як ми можемо таким чином надати власний індексний номер, замість цього ми можемо використовувати,

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

Будь ласка, відредагуйте свою відповідь та додайте контекст, пояснивши, як ваша відповідь вирішує проблему, а не публікувати відповідь лише для коду. З перегляду
Педрам Парсіан

Навіщо робити непотрібні компенсації? Чому б не просто string[string.startIndex]? До речі, код не буде правильно поводитись / компілювати, оскільки ви використовували два різні назви змінних.
Крістік

2

Швидкий 4.2 або новішої версії

Підписка діапазону та часткового діапазону за допомогою властивості String'sindices

Як варіант приємної відповіді @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, але не дуже swiftenvшвидко Swift в наші дні: / Я використовую коли я це роблю, хоча, я думаю, він буде оновлений з 4.2 також дещо скоро.
dfri

1
@LeoDabus дякую за оновлення цієї відповіді до сучасного Swift!
dfri

1
@LeoDabus приємної роботи! Пізніше доведеться вивчити деталі, але я пам’ятаю, що мені ніколи не сподобалося, що нам довелося повернутись на Foundation деякі з упорядкованих / лічильних типів наборів.
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

Пітоноподібне рішення, яке дозволяє використовувати негативний індекс,

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

Ви також можете конвертувати 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)]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.