Мы продолжаем публикацию материалов, от разработчиков библиотеки MSLibrary for iOS. Тема этой статьи не случайна, проблема выбора нескольких условий из заданного множества, не редко встречается в нашей работе. Простейший пример — выбор партнера для игры (свидания, путешествия и тд). Выбор надо осуществлять из нескольких групп, сформированных по уровню подготовленности (здесь могут быть и возрастные группы и все что угодно). Условие — дать пользователю возможность выбрать партнера из одной или нескольких групп одновременно. Другим примером могут служить константы NSRegularExpressionOptions проверки типа данных для класса NSRegularExpression. При подстановке этих констант в методы класса, мы можем записать:
Объединив константы знаком логического «ИЛИ» мы будем уверены, что проверим анализируемую строку на соответствие обоим из заданных условий.
Один из способов реализации подобной задачи — использование списка констант в виде перечисления enum, в котором элементы перечисления представляют собой двоичные числа с одним установленным битом. Сделать это не очень сложно, но сначала немного теории. Вспомним такие битовые операции, как «СДВИГ», «И», «ИЛИ».
Побитовые логические операции с двоичными числами
ЛОГИЧЕСКИЙ БИТОВЫЙ «СДВИГ (SHIFT)»
На картинке изображен логический сдвиг влево на один разряд.
Для полноты картины можно сказать, что при сдвиге влево на один разряд исходное число 01010101 превращается в 10101010. В привычной нам шестнадцатеричный системе (Hex) число 0x55 превращается в 0xAA или в десятичной системе 85 превращается в 170, то есть умножается на 2, что вполне логично.
ПОБИТОВОЕ «ИЛИ (OR)»
Визуально это выглядит так:
Применение операции побитового «ИЛИ» к исходным числам 01100110 и 10101010 дает результат 11101110. В шестнадцатеричный системе (Hex) исходные числа 0x66 и 0xAA, результат 0xEE или в десятичной системе исходные числа 102 и 170, результат 238.
ПОБИТОВОЕ «И (AND)»
Применение операции побитового «И» к исходным числам 01100110 и 10101010 дает результат 00100010. В шестнадцатеричный системе (Hex) исходные числа 0x66 и 0xAA, результат 0x22 или в десятичной системе исходные числа 102 и 170, результат 34.
Двоичные числа с одним установленным битом
Теперь посмотрим что же получается в случае применения этих операций к двоичным числам с одним установленным битом.
ПОРАЗРЯДНЫЙ (ПОБИТОВЫЙ) СДВИГ
Примененный к числу 00000001 поразрядный сдвиг даст следующий результат:
Операции битовых сдвигов обозначаются, в зависимости от направления сдвига, знаками "<<" и ">>":
Число 00000001 это десятичная 1. Поэтому числа с одним установленным битом и битовым сдвигом влево принято обозначать следующим образом:
ПОБИТОВОЕ ЛОГИЧЕСКОЕ «ИЛИ (OR)»
Возьмем несколько чисел с одним установленным битом и применим к ним операцию побитового логического «ИЛИ (OR)». Записывается это так:
а выглядит так:
Замечательное свойство такой операции — в результате получается уникальное значение в данном диапазоне чисел.
ПОБИТОВОЕ ЛОГИЧЕСКОЕ «И (AND)»
Второе свойство чисел с одним установленным битом — применив операцию побитового логического «И (AND)» к любому из чисел, входящих в операцию «ИЛИ (OR)» и полученному результату, мы получим исходное число:
В то время, как другие числа с одним установленным битом этому условию не удовлетворяют:
Для наглядности:
Это свойство числа, полученного в результате применения операции побитового логического «ИЛИ (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)»
При логическом сдвиге значение последнего бита по направлению сдвига теряется (копируясь в бит переноса), а первый приобретает нулевое значение.
На картинке изображен логический сдвиг влево на один разряд.
Для полноты картины можно сказать, что при сдвиге влево на один разряд исходное число 01010101 превращается в 10101010. В привычной нам шестнадцатеричный системе (Hex) число 0x55 превращается в 0xAA или в десятичной системе 85 превращается в 170, то есть умножается на 2, что вполне логично.
ПОБИТОВОЕ «ИЛИ (OR)»
Это — бинарная операция, действие которой эквивалентно применению логического ИЛИ к каждой паре битов, которые стоят на одинаковых позициях в двоичных представлениях операндов. Другими словами, если оба соответствующих бита операндов равны 0, двоичный разряд результата равен 0; если же хотя бы один бит из пары равен 1, двоичный разряд результата равен 1.
Визуально это выглядит так:
Применение операции побитового «ИЛИ» к исходным числам 01100110 и 10101010 дает результат 11101110. В шестнадцатеричный системе (Hex) исходные числа 0x66 и 0xAA, результат 0xEE или в десятичной системе исходные числа 102 и 170, результат 238.
ПОБИТОВОЕ «И (AND)»
Это — бинарная операция, действие которой эквивалентно применению логического «И» к каждой паре битов, которые стоят на одинаковых позициях в двоичных представлениях операндов. Другими словами, если оба соответствующих бита операндов равны 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
ПРОСТО: удаляем из строки ненужные символы, для iOS и не только…