Мы продолжаем публикацию материалов, от разработчиков библиотеки MSLibrary for iOS. Тема этой статьи не случайна, проблема выбора нескольких условий из заданного множества, не редко встречается в нашей работе. Простейший пример — выбор партнера для игры (свидания, путешествия и тд). Выбор надо осуществлять из нескольких групп, сформированных по уровню подготовленности (здесь могут быть и возрастные группы и все что угодно). Условие — дать пользователю возможность выбрать партнера из одной или нескольких групп одновременно. Другим примером могут служить константы NSRegularExpressionOptions проверки типа данных для класса NSRegularExpression. При подстановке этих констант в методы класса, мы можем записать:
Объединив константы знаком логического «ИЛИ» мы будем уверены, что проверим анализируемую строку на соответствие обоим из заданных условий.
Один из способов реализации подобной задачи — использование списка констант в виде перечисления enum, в котором элементы перечисления представляют собой двоичные числа с одним установленным битом. Сделать это не очень сложно, но сначала немного теории. Вспомним такие битовые операции, как «СДВИГ», «И», «ИЛИ».
Побитовые логические операции с двоичными числами
ЛОГИЧЕСКИЙ БИТОВЫЙ «СДВИГ (SHIFT)»
На картинке изображен логический сдвиг влево на один разряд.
![](https://habrastorage.org/files/823/402/187/82340218750b410d80c0535c95fb64ee.png)
Для полноты картины можно сказать, что при сдвиге влево на один разряд исходное число 01010101 превращается в 10101010. В привычной нам шестнадцатеричный системе (Hex) число 0x55 превращается в 0xAA или в десятичной системе 85 превращается в 170, то есть умножается на 2, что вполне логично.
ПОБИТОВОЕ «ИЛИ (OR)»
Визуально это выглядит так:
![](https://habrastorage.org/files/09e/756/ee9/09e756ee92c34ec2bedd6f821ee5c836.png)
Применение операции побитового «ИЛИ» к исходным числам 01100110 и 10101010 дает результат 11101110. В шестнадцатеричный системе (Hex) исходные числа 0x66 и 0xAA, результат 0xEE или в десятичной системе исходные числа 102 и 170, результат 238.
ПОБИТОВОЕ «И (AND)»
![](https://habrastorage.org/files/0ea/41c/66f/0ea41c66fdd24954a5c4ce96d4672d20.png)
Применение операции побитового «И» к исходным числам 01100110 и 10101010 дает результат 00100010. В шестнадцатеричный системе (Hex) исходные числа 0x66 и 0xAA, результат 0x22 или в десятичной системе исходные числа 102 и 170, результат 34.
Двоичные числа с одним установленным битом
Теперь посмотрим что же получается в случае применения этих операций к двоичным числам с одним установленным битом.
ПОРАЗРЯДНЫЙ (ПОБИТОВЫЙ) СДВИГ
Примененный к числу 00000001 поразрядный сдвиг даст следующий результат:
![](https://habrastorage.org/files/d28/092/39c/d2809239cd1c45e5a3df3d5bd40e066f.png)
Операции битовых сдвигов обозначаются, в зависимости от направления сдвига, знаками "<<" и ">>":
Число 00000001 это десятичная 1. Поэтому числа с одним установленным битом и битовым сдвигом влево принято обозначать следующим образом:
ПОБИТОВОЕ ЛОГИЧЕСКОЕ «ИЛИ (OR)»
Возьмем несколько чисел с одним установленным битом и применим к ним операцию побитового логического «ИЛИ (OR)». Записывается это так:
а выглядит так:
![](https://habrastorage.org/files/696/cf6/93e/696cf693eb2d4c638df2f50b79530859.png)
Замечательное свойство такой операции — в результате получается уникальное значение в данном диапазоне чисел.
ПОБИТОВОЕ ЛОГИЧЕСКОЕ «И (AND)»
Второе свойство чисел с одним установленным битом — применив операцию побитового логического «И (AND)» к любому из чисел, входящих в операцию «ИЛИ (OR)» и полученному результату, мы получим исходное число:
В то время, как другие числа с одним установленным битом этому условию не удовлетворяют:
Для наглядности:
![](https://habrastorage.org/files/e05/ce4/a04/e05ce4a04d304c2ea4d5f239611df784.png)
Это свойство числа, полученного в результате применения операции побитового логического «ИЛИ (OR)», позволяет использовать его в качестве «БИТОВОЙ МАСКИ».
БИТОВАЯ МАСКА
Другим словами, результат, полученный от применения операции побитового логического «ИЛИ» к разным числам с одним установленным битом из заданного множества чисел, может быть использован в качестве битовой маски.
Перейдем к практике.
Перечисления enum с двоичными числами с одним установленным битом
Для определения набора констант, с заданным количеством конкретных значений, удобно использовать перечисляемый тип или перечисление enum. Запишем перечисление для умозрительного примера, приведенного в начале статьи. Допустим нам надо определить пять групп пользователей. Записывается это так:
Мы можем использовать «Groups » для выбора подходящей группы пользователей, но не можем объединять эти группы, то есть мы не можем выбрать пользователей из групп «group_2» и «group_3» и организовать фильтрацию всех пользователей по этим параметрам. Мы можем вычислить контрольное число произведя операцию (2 + 3) = 5, но оно не будет уникальным, группы «group_1» и «group_4» дадут тот же результат: (1 + 4) = 5. Модифицируем выражение, указав в качестве значений числа с одним установленным битом и используя побитовый сдвиг влево.
В этом случае мы можем легко создать «БИТОВУЮ МАСКУ», применив операции побитового «ИЛИ (OR)» к выбранным параметрам. Допустим, мы выбрали те же «group_2» и «group_3»:
или, подставив константы:
Аналогично устроены многие битовые константы, в частности упоминавшиеся в начале статьи NSRegularExpressionOptions:
или NSTextCheckingResult:
Вернемся к нашему примеру с группами пользователей. Мы создали битовую маску, теперь надо написать код для ее использования. Если два подхода для решения этой задачи, первый — использующий операторы «if(){}» и второй — в котором применяется связка операторов «for(){}» и «switch(){}», рассмотрим оба.
Использование оператора «if(){}»
Этот подход довольно несложен. Нижеприведенный код в особых комментариях не нуждается:
Подставив в этот код значения констант мы увидим, как он работает:
Таким образом, некие действия выполняются во всех случаях соответствия константы и маски, в нашем примере для пользователей из групп «group_2» и «group_3».
Использование операторов «for(){}» и «switch(){}»
Второй подход заключается в использовании связки операторов «for(){}» и «switch(){}». Преимущество этого варианта в том, что если для разных констант «Groups» необходимо совершать идентичные действия, например применять одни и те же функции, отличающиеся только некими переменными, то данный подход позволяет создать более компактный и изящный код:
По такому же принципу организованы многие методы и функции нашей библиотеки MSLibrary for iOS. Для примера, функция: msfDDstringCheckingStyle() принимает значения «YES» или «NO» в зависимости от того, выполняются ли в анализируемой строке заданные условия.
где
string — анализируемая строка
stringCheckingStyle — константы, накладывающие некие ограничения
allConditionsIsRequired — флаг, в случае если он имеет значение «YES», выполнение условий определяемых всеми константами «stringCheckingStyle» обязательно, если он имеет значение «NO», может быть выполнено любое одно или несколько, заданных условий
minLengthOfString — минимальная длина строки
Константы «stringCheckingStyle» заданы так:
Таким образом, например, записав функцию с виде:
мы получим положительный результат только в случае, если строка «string» будет иметь не менее 8 знаков и в ней будут обязательно содержаться буквы английского алфавита и цифры, что полезно, к примеру, при проверке новых паролей.
Как видите вопрос решается всего в одну строку кода.
Надеемся, что материал был для вас полезен, команда MSLibrary for iOS
Другие статьи:
Захват и верификация телефонных номеров с помощью регулярных выражений, для iOS и не только… Часть 1
Захват и верификация телефонных номеров с помощью регулярных выражений, для iOS и не только… Часть 2
ПРОСТО: удаляем из строки ненужные символы, для iOS и не только…
NSRegularExpressionCaseInsensitive | NSRegularExpressionDotMatchesLineSeparators
Объединив константы знаком логического «ИЛИ» мы будем уверены, что проверим анализируемую строку на соответствие обоим из заданных условий.
Один из способов реализации подобной задачи — использование списка констант в виде перечисления enum, в котором элементы перечисления представляют собой двоичные числа с одним установленным битом. Сделать это не очень сложно, но сначала немного теории. Вспомним такие битовые операции, как «СДВИГ», «И», «ИЛИ».
Побитовые логические операции с двоичными числами
ЛОГИЧЕСКИЙ БИТОВЫЙ «СДВИГ (SHIFT)»
При логическом сдвиге значение последнего бита по направлению сдвига теряется (копируясь в бит переноса), а первый приобретает нулевое значение.
На картинке изображен логический сдвиг влево на один разряд.
![](https://habrastorage.org/files/823/402/187/82340218750b410d80c0535c95fb64ee.png)
Для полноты картины можно сказать, что при сдвиге влево на один разряд исходное число 01010101 превращается в 10101010. В привычной нам шестнадцатеричный системе (Hex) число 0x55 превращается в 0xAA или в десятичной системе 85 превращается в 170, то есть умножается на 2, что вполне логично.
ПОБИТОВОЕ «ИЛИ (OR)»
Это — бинарная операция, действие которой эквивалентно применению логического ИЛИ к каждой паре битов, которые стоят на одинаковых позициях в двоичных представлениях операндов. Другими словами, если оба соответствующих бита операндов равны 0, двоичный разряд результата равен 0; если же хотя бы один бит из пары равен 1, двоичный разряд результата равен 1.
Визуально это выглядит так:
![](https://habrastorage.org/files/09e/756/ee9/09e756ee92c34ec2bedd6f821ee5c836.png)
Применение операции побитового «ИЛИ» к исходным числам 01100110 и 10101010 дает результат 11101110. В шестнадцатеричный системе (Hex) исходные числа 0x66 и 0xAA, результат 0xEE или в десятичной системе исходные числа 102 и 170, результат 238.
ПОБИТОВОЕ «И (AND)»
Это — бинарная операция, действие которой эквивалентно применению логического «И» к каждой паре битов, которые стоят на одинаковых позициях в двоичных представлениях операндов. Другими словами, если оба соответствующих бита операндов равны 1, результирующий двоичный разряд равен 1; если же хотя бы один бит из пары равен 0, результирующий двоичный разряд равен 0.
![](https://habrastorage.org/files/0ea/41c/66f/0ea41c66fdd24954a5c4ce96d4672d20.png)
Применение операции побитового «И» к исходным числам 01100110 и 10101010 дает результат 00100010. В шестнадцатеричный системе (Hex) исходные числа 0x66 и 0xAA, результат 0x22 или в десятичной системе исходные числа 102 и 170, результат 34.
Двоичные числа с одним установленным битом
Теперь посмотрим что же получается в случае применения этих операций к двоичным числам с одним установленным битом.
ПОРАЗРЯДНЫЙ (ПОБИТОВЫЙ) СДВИГ
Примененный к числу 00000001 поразрядный сдвиг даст следующий результат:
![](https://habrastorage.org/files/d28/092/39c/d2809239cd1c45e5a3df3d5bd40e066f.png)
Операции битовых сдвигов обозначаются, в зависимости от направления сдвига, знаками "<<" и ">>":
двоичноеЧисло << 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
а выглядит так:
![](https://habrastorage.org/files/696/cf6/93e/696cf693eb2d4c638df2f50b79530859.png)
Замечательное свойство такой операции — в результате получается уникальное значение в данном диапазоне чисел.
Результат, полученный от применения операции побитового логического «ИЛИ» к разным числам с одним установленным битом из заданного множества чисел, всегда уникален
ПОБИТОВОЕ ЛОГИЧЕСКОЕ «И (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
Для наглядности:
![](https://habrastorage.org/files/e05/ce4/a04/e05ce4a04d304c2ea4d5f239611df784.png)
Это свойство числа, полученного в результате применения операции побитового логического «ИЛИ (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
ПРОСТО: удаляем из строки ненужные символы, для iOS и не только…