Мы продолжаем публикацию материалов, от разработчиков библиотеки MSLibrary for iOS. Тема этой статьи не случайна, проблема выбора нескольких условий из заданного множества, не редко встречается в нашей работе. Простейший пример — выбор партнера для игры (свидания, путешествия и тд). Выбор надо осуществлять из нескольких групп, сформированных по уровню подготовленности (здесь могут быть и возрастные группы и все что угодно). Условие — дать пользователю возможность выбрать партнера из одной или нескольких групп одновременно. Другим примером могут служить константы NSRegularExpressionOptions проверки типа данных для класса NSRegularExpression. При подстановке этих констант в методы класса, мы можем записать:
NSRegularExpressionCaseInsensitive | NSRegularExpressionDotMatchesLineSeparators
Объединив константы знаком логического «ИЛИ» мы будем уверены, что проверим анализируемую строку на соответствие обоим из заданных условий.
Один из способов реализации подобной задачи — использование списка констант в виде перечисления enum, в котором элементы перечисления представляют собой двоичные числа с одним установленным битом. Сделать это не очень сложно, но сначала немного теории. Вспомним такие битовые операции, как «СДВИГ», «И», «ИЛИ», «НЕ».
Побитовые логические операции с двоичными числами
ЛОГИЧЕСКИЙ БИТОВЫЙ «СДВИГ (SHIFT)»
При логическом сдвиге значение последнего бита по направлению сдвига теряется (копируясь в бит переноса), а первый приобретает нулевое значение.
На картинке изображен логический сдвиг влево на один разряд.
Для полноты картины можно сказать, что при сдвиге влево на один разряд исходное число 01010101 превращается в 10101010. В привычной нам шестнадцатеричный системе (Hex) число 0x55 превращается в 0xAA или в десятичной системе 85 превращается в 170, то есть умножается на 2, что вполне логично.
Это — бинарная операция, действие которой эквивалентно применению логического ИЛИ к каждой паре битов, которые стоят на одинаковых позициях в двоичных представлениях операндов. Другими словами, если оба соответствующих бита операндов равны 0, двоичный разряд результата равен 0; если же хотя бы один бит из пары равен 1, двоичный разряд результата равен 1.
Визуально это выглядит так:
Применение операции побитового «ИЛИ» к исходным числам 01100110 и 10101010 дает результат 01110110. В шестнадцатеричный системе (Hex) исходные числа 0x66 и 0xAA, результат 0x76 или в десятичной системе исходные числа 102 и 170, результат 118.
Это — бинарная операция, действие которой эквивалентно применению логического «И» к каждой паре битов, которые стоят на одинаковых позициях в двоичных представлениях операндов. Другими словами, если оба соответствующих бита операндов равны 1, результирующий двоичный разряд равен 1; если же хотя бы один бит из пары равен 0, результирующий двоичный разряд равен 0.
Применение операции побитового «И» к исходным числам 01100110 и 10101010 дает результат 00100010. В шестнадцатеричный системе (Hex) исходные числа 0x66 и 0xAA, результат 0x22 или в десятичной системе исходные числа 102 и 170, результат 34.
Двоичные числа с одним установленным битом
Теперь посмотрим что же получается в случае, применения этих операций к двоичным числам с одним установленным битом.
ПОРАЗРЯДНЫЙ (ПОБИТОВЫЙ) СДВИГ
Примененный к числу 00000001 поразрядный сдвиг даст следующий результат:
Операции битовых сдвигов обозначаются знаками "<<" и ">>":
двоичноеЧисло << n // битовый сдвиг влево на "n" позиций (разрядов) двоичноеЧисло >> n // битовый сдвиг вправо на "n" позиций (разрядов)
Число 00000001 это десятичная 1. Поэтому числа с одним установленным битом и битовым сдвигом влево принято записывать так:
1 << n //где "n" количество позиций (разрядов) на которое осуществляется сдвиг 1 << 0 // 00000001 сдвиг на 0 разрядов 1 << 1 // 00000001 сдвиг на 1 разрядов 1 << 3 // 00000001 сдвиг на 3 разряда 1 << 5 // 00000001 сдвиг на 5 разрядов
ПОБИТОВОЕ ЛОГИЧЕСКОЕ «ИЛИ (OR)»
Возьмем несколько чисел с одним установленным битом и применим к ним операцию побитового логического «ИЛИ (OR)». Записывается это так:
1<<0 | 1<<3 | 1<<5
а выглядит так:
Замечательное свойство такой операции — в результате получается уникальное значение в данном диапазоне чисел.
Результат, полученный от применения операции побитового логического «ИЛИ» к разным числам с одним установленным битом из заданного множества чисел, всегда уникален
ПОБИТОВОЕ ЛОГИЧЕСКОЕ «И (AND)»
Второе свойство чисел с одним установленным битом, это то, что применив операцию побитового логического «И (AND)» к любому из чисел, входящих в операцию «ИЛИ (OR)» и полученному результату, мы получим исходное число:
1 << 0 & (1 << 0 | 1 << 3 | 1 << 5) = 1 << 0 1 << 3 & (1 << 0 | 1 << 3 | 1 << 5) = 1 << 3 1 << 5 & (1 << 0 | 1 << 3 | 1 << 5) = 1 << 5
В то время, как другие числа с одним установленным битом этому условию не удовлетворяют:
1 << 1 & (1 << 0 | 1 << 3 | 1 << 5) ≠ 1 << 1 1 << 2 & (1 << 0 | 1 << 3 | 1 << 5) ≠ 1 << 2 1 << 4 & (1 << 0 | 1 << 3 | 1 << 5) ≠ 1 << 4
Для наглядности:
Это свойство числа, полученного в результате применения операции побитового логического «ИЛИ (OR)», позволяет использовать его в качестве «БИТОВОЙ МАСКИ».
Это — определённые данные, которые используются для маскирования — выбора отдельных битов или полей из нескольких битов из двоичной строки или числа.
Другим словами, результат, полученный от применения операции побитового логического «ИЛИ» к разным числам с одним установленным битом из заданного множества чисел, может быть использован в качестве битовой маски.
Применив операцию побитового логического «ИЛИ (OR)» к нескольким числам с одним установленным битом, можно использовать полученный результат в качестве битовой маски для фильтрации исходных чисел из множества других.
Перейдем к практике.
Перечисления enum с двоичными числами с одним установленным битом
Для определения набора констант, определенным количеством конкретных значений, удобно использовать перечисляемый тип или перечисление enum. Запишем перечисление для умозрительного примера, приведенного в начале статьи. Допустим нам надо определить пять групп пользователей. Записывается это так:
enum Groups { group_0 = 0, group_1 = 1 group_2 = 2, group_3 = 3, group_4 = 4, };
Мы можем использовать «Groups » для выбора подходящей группы пользователей, но не можем объединять эти группы, то есть мы не можем выбрать пользователей из групп «group_2» и «group_3» и организовать фильтрацию всех пользователей по этим параметрам. Мы можем вычислить контрольное число произведя операцию (2 + 3) = 5, но оно не будет уникальным, группы «group_1» и «group_4» дадут тот же результат: (1 + 4) = 5. Модифицируем выражение, указав в качестве значений числа с одним установленным битом и используя побитовый сдвиг влево.
enum Groups { group_0 = 1 << 0, group_1 = 1 << 1, group_2 = 1 << 2, group_3 = 1 << 3, group_4 = 1 << 4 };
В этом случае мы можем легко создать «БИТОВУЮ МАСКУ», применив операции побитового «ИЛИ (OR)» к выбранным параметрам. Допустим, мы выбрали те же «group_2» и «group_3»:
(1 << 2 | 1 << 3) = 0x55 1 << 2 & (1 << 2 | 1 << 3) = 1 << 2 1 << 3 & (1 << 2 | 1 << 3) = 1 << 3 1 << 1 & (1 << 2 | 1 << 3) ≠ 1 << 1 1 << 4 & (1 << 2 | 1 << 3) ≠ 1 << 4
или, подставив константы:
(group_2 | group_3) = 0x55 group_2 & (group_2 | group_3) = group_2 group_3 & (group_2 | group_3) = group_3 group_1 & (group_2 | group_3) ≠ group_1 group_4 & (group_2 | group_3) ≠ group_4
Аналогично устроены многие битовые константы, в частности упоминавшиеся в начале статьи NSRegularExpressionOptions:
typedef NS_OPTIONS(NSUInteger, NSRegularExpressionOptions) { NSRegularExpressionCaseInsensitive = 1 << 0, // Match letters in the pattern independent of case NSRegularExpressionAllowCommentsAndWhitespace = 1 << 1, // Ignore whitespace and #-prefixed comments in the pattern NSRegularExpressionIgnoreMetacharacters = 1 << 2, // Treat the entire pattern as a literal string NSRegularExpressionDotMatchesLineSeparators = 1 << 3, // Allow . to match any character, including line separators NSRegularExpressionAnchorsMatchLines = 1 << 4, // Allow ^ and $ to match the start and end of lines NSRegularExpressionUseUnixLineSeparators = 1 << 5, // Treat only \n as a line separator (otherwise, all standard line separators are used) NSRegularExpressionUseUnicodeWordBoundaries = 1 << 6 // Use Unicode TR#29 to specify word boundaries (otherwise, traditional regular expression word boundaries are used) };
или NSTextCheckingResult:
typedef NS_OPTIONS(uint64_t, NSTextCheckingType) { // a single type NSTextCheckingTypeOrthography = 1ULL << 0, // language identification NSTextCheckingTypeSpelling = 1ULL << 1, // spell checking NSTextCheckingTypeGrammar = 1ULL << 2, // grammar checking NSTextCheckingTypeDate = 1ULL << 3, // date/time detection NSTextCheckingTypeAddress = 1ULL << 4, // address detection NSTextCheckingTypeLink = 1ULL << 5, // link detection NSTextCheckingTypeQuote = 1ULL << 6, // smart quotes NSTextCheckingTypeDash = 1ULL << 7, // smart dashes NSTextCheckingTypeReplacement = 1ULL << 8, // fixed replacements, such as copyright symbol for (c) NSTextCheckingTypeCorrection = 1ULL << 9, // autocorrection NSTextCheckingTypeRegularExpression = 1ULL << 10, // regular expression matches NSTextCheckingTypePhoneNumber = 1ULL << 11, // phone number detection NSTextCheckingTypeTransitInformation = 1ULL << 12 // transit (e.g. flight) info detection };
Вернемся к нашему примеру с группами пользователей. Мы создали битовую маску, теперь надо написать код для ее использования. Если два подхода для решения этой задачи, первый — использующий операторы «if(){}»и второй — в котором применяется связка операторов «for(){}» и «switch(){}», рассмотрим оба.
Использование оператора «if(){}»
Этот подход довольно несложен. Нижеприведенный код в особых комментариях не нуждается:
NSInteger group_masck = (group_2 | group_3) = 0x55; if ((group_masck & group_0) == group_0) { // действие, совершаемое в случае соответствия константы и маски } if ((group_masck & group_1) == group_1) { // действие, совершаемое в случае соответствия константы и маски } if ((group_masck & group_2) == group_2) { // действие, совершаемое в случае соответствия константы и маски } if ((group_masck & group_3) == group_3) { // действие, совершаемое в случае соответствия константы и маски } if ((group_masck & group_4) == group_4) { // действие, совершаемое в случае соответствия константы и маски }
Подставив в этот код значения констант мы увидим, как он работает:
NSInteger group_masck = (1 << 2 | 1 << 3) = 0x55 if (((1 << 2 | 1 << 3) & 1 << 0) == 1 << 0) { // действие, совершаемое в случае соответствия константы и маски } if (((1 << 2 | 1 << 3) & 1 << 1) == 1 << 1) { // действие, совершаемое в случае соответствия константы и маски } if (((1 << 2 | 1 << 3) & 1 << 2) == 1 << 2) { // действие, совершаемое в случае соответствия константы и маски } if (((1 << 2 | 1 << 3) & 1 << 3) == 1 << 3) { // действие, совершаемое в случае соответствия константы и маски } if (((1 << 2 | 1 << 3) & 1 << 4) == 1 << 4) { // действие, совершаемое в случае соответствия константы и маски }
Таким образом, некие действия выполняются во всех случаях соответствия константы и маски, в нашем примере для пользователей из групп «group_2» и «group_3».
Использование операторов «for(){}» и «switch(){}»
Второй подход заключается в использовании связки операторов «for(){}» и «switch(){}». Преимущество этого варианта в том, что если для разных констант «Groups» необходимо совершать идентичные действия, например применять одни и те же функции, отличающиеся только некими переменными, то данный подход позволяет создать более компактный и изящный код:
NSInteger group_masck = (group_2 | group_3) = 0x55; id variable; for (int i = 0; i <= 4; i++) { switch (i) { case 0: { variable = value_0; } break; case 1: { variable = value_1; } break; case 2: { variable = value_2; } break; case 3: { variable = value_3; } break; case 4: { variable = value_4; } break; default: { //действие, совершаемое в случае ошибки } break; } if ((group_masck & 1ULL << i) == 1ULL << i) { // выполняется некое действие с подстановкой переменной "variable" } }
По такому же принципу организованы многие методы и функции нашей библиотеки MSLibrary for iOS. Для примера, функция: msfDDstringCheckingStyle() принимает значения «YES» или «NO» в зависимости от того, выполняются ли в анализируемой строке заданные условия.
BOOL msfDDstringCheckingStyle(NSString *string, tMSstringCheckingStyle stringCheckingStyle, BOOL allConditionsIsRequired, NSInteger minLengthOfString)
где
string — анализируемая строка
stringCheckingStyle — константы, накладывающие некие ограничения
allConditionsIsRequired — флаг, в случае если он имеет значение «YES», выполнение условий определяемых всеми константами «stringCheckingStyle» обязательно, если он имеет значение «NO», может быть выполнено любое одно или несколько, заданных условий
minLengthOfString — минимальная длина строки
Константы «stringCheckingStyle» заданы так:
typedef enum tMSstringCheckingStyle: NSInteger { kMSstringCheckingStyle_digits = 1ULL << 0, // must-have only a digits kMSstringCheckingStyle_englishLetters = 1ULL << 1, // must-have only a English letters kMSstringCheckingStyle_russianLetters = 1ULL << 2, // must-have only a Russian letters kMSstringCheckingStyle_startWithLetter = 1ULL << 3, // the string necessarily start with a letter kMSstringCheckingStyle_upperAndLowerCaseLetters = 1ULL << 4, // must-have a uppercase and a lowercase letters kMSstringCheckingStyle_specialSymbols = 1ULL << 5, // must-have one or more special symbols "-" "." "+" "_" } tMSstringCheckingStyle;
Таким образом, например, записав функцию с виде:
msfDDstringCheckingStyle(NSString *string, tMSstringCheckingStyle kMSstringCheckingStyle_digits | kMSstringCheckingStyle_englishLetters, BOOL YES, NSInteger 8)
мы получим положительный результат только в случае, если строка «string» будет иметь не менее 8 знаков и в ней будут обязательно содержаться буквы английского алфавита и цифры, что полезно, к примеру, при проверке новых паролей. Как видите вопрос решается всего в одну строку кода.
Надеемся, что материал был для вас полезен, команда MSLibrary for iOS
Другие статьи:
Захват и верификация телефонных номеров с помощью регулярных выражений, для iOS и не только… Часть 1
Захват и верификация телефонных номеров с помощью регулярных выражений, для iOS и не только… Часть 2
ссылка на оригинал статьи https://habrahabr.ru/post/279441/
Добавить комментарий