Есть ли простой способ указать символьные литералы в Swift?

Swift, похоже, пытается обесценить понятие строки, состоящей из массива атомных символов, что имеет смысл для многих применений, но там очень много программирования, которое включает в себя сбор данных по объектам, которые являются ASCII для всех практических целей: особенно с файловыми вводами/выводами. Отсутствие встроенной функции языка для указания символьного литерала кажется щелевой дырой, т.е. Нет аналога C/Java/etc-esque:

String foo="a"
char bar='a'

Это довольно неудобно, потому что даже если вы преобразуете свои строки в массивы символов, вы не можете делать такие вещи, как:

let ch:unichar = arrayOfCharacters[n]
if ch >= 'a' && ch <= 'z' {...whatever...}

Один довольно хакерский способ обхода - сделать что-то вроде этого:

let LOWCASE_A = ("a" as NSString).characterAtIndex(0)
let LOWCASE_Z = ("z" as NSString).characterAtIndex(0)
if ch >= LOWCASE_A && ch <= LOWCASE_Z {...whatever...}

Это работает, но, очевидно, это довольно уродливо. Кто-нибудь имеет лучший способ?

Ответ 1

Character может быть создан из String, если те String состоят только из одного символа. И, поскольку Character реализует ExtendedGraphemeClusterLiteralConvertible, Swift сделает это для вас автоматически при назначении. Итак, чтобы создать Character в Swift, вы можете просто сделать что-то вроде:

let ch: Character = "a"

Затем вы можете использовать метод contains для IntervalType (сгенерированный с помощью Range операторов), чтобы проверить, символ находится в пределах диапазона, который вы ищете:

if ("a"..."z").contains(ch) {
    /* ... whatever ... */
}

Пример:

let ch: Character = "m"
if ("a"..."z").contains(ch) {
    println("yep")
} else {
    println("nope")
}

Выходы:

да


Обновление: Как указывал @MartinR, упорядочение символов Swift основано на Unicode Normalization Form D который не в том же порядке, что и коды символов ASCII. В вашем конкретном случае между a и z больше символов, чем в прямом ASCII (например, ä). Подробнее см. @MartinR здесь.

Если вам нужно проверить, находится ли символ между двумя символьными кодами ASCII, вам может понадобиться сделать что-то вроде вашего обходного пути. Однако вам также придется преобразовать ch в unichar, а не в Character, чтобы он работал (см. этот вопрос для получения дополнительной информации о Character vs unichar):

let a_code = ("a" as NSString).characterAtIndex(0)
let z_code = ("z" as NSString).characterAtIndex(0)
let ch_code = (String(ch) as NSString).characterAtIndex(0)

if (a_code...z_code).contains(ch_code) {
    println("yep")
} else {
    println("nope")
}

Или, еще более подробный способ без использования NSString:

let startCharScalars = "a".unicodeScalars
let startCode = startCharScalars[startCharScalars.startIndex]

let endCharScalars = "z".unicodeScalars
let endCode = endCharScalars[endCharScalars.startIndex]

let chScalars = String(ch).unicodeScalars
let chCode = chScalars[chScalars.startIndex]

if (startCode...endCode).contains(chCode) {
    println("yep")
} else {
    println("nope")
}

Примечание. Оба этих примера работают только в том случае, если символ содержит только одну кодовую точку, но при условии, что мы ограничены ASCII, это не должно быть проблемой. p >

Ответ 2

Если вам нужны литералы ASCII C-стиля, вы можете просто сделать это:

let chr = UInt8(ascii:"A") // == UInt8( 0x41 )

Или, если вам нужны 32-разрядные литералы в Юникоде, вы можете сделать это:

let unichr1 = UnicodeScalar("A").value // == UInt32( 0x41 )
let unichr2 = UnicodeScalar("é").value // == UInt32( 0xe9 )
let unichr3 = UnicodeScalar("😀").value // == UInt32( 0x1f600 )

Или 16-бит:

let unichr1 = UInt16(UnicodeScalar("A").value) // == UInt16( 0x41 )
let unichr2 = UInt16(UnicodeScalar("é").value) // == UInt16( 0xe9 )

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

Ответ 3

Предложенную вами функцию предлагалось включить в Swift 5.1, но это предложение было отклонено по нескольким причинам:

  1. неоднозначность

    Предложение, как написано, в текущей экосистеме Swift, позволило бы использовать выражения типа 'x' + 'y' == "xy", которые не были предназначены (правильный синтаксис был бы "x" + "y" == "xy").

  2. укрупнение

    Предложение было два в одном.

    Во-первых, он предложил способ введения литералов в одну кавычку в язык.

    Во-вторых, он предложил преобразовать их в числовые типы для работы со значениями ASCII и кодовыми точками Unicode.

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

  3. разногласие

    Он так и не достиг консенсуса относительно того, будет ли тип по умолчанию 'x' Character или Unicode.Scalar. Предложение пошло с Character, цитируя Принцип Наименьшего Сюрприза, несмотря на это отсутствие консенсуса.

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


Синтаксис может выглядеть следующим образом:

let myChar = 'f' // Type is Character, value is solely the unicode U+0066 LATIN SMALL LETTER F
let myInt8: Int8 = 'f' // Type is Int8, value is 102 (0x66)
let myUInt8Array: [UInt8] = [ 'a', 'b', '1', '2' ] // Type is [UInt8], value is [ 97, 98, 49, 50 ] ([ 0x61, 0x62, 0x31, 0x32 ])

switch someUInt8 {
    case 'a' ... 'f': return "Lowercase hex letter"
    case 'A' ... 'F': return "Uppercase hex letter"
    case '0' ... '9': return "Hex digit"
    default: return "Non-hex character"
}

Ответ 4

Также похоже, что вы можете использовать следующий синтаксис:

Character("a")

Это создаст Character из указанной строки одного символа.

Я проверял это только в Swift 4 и Xcode 10.1