MSLibrary. Реализация множественного выбора условий с помощью битовых масок, для iOS и не только…

от автора

Мы продолжаем публикацию материалов, от разработчиков библиотеки MSLibrary for iOS. Тема этой статьи не случайна, проблема выбора нескольких условий из заданного множества, не редко встречается в нашей работе. Простейший пример — выбор партнера для игры (свидания, путешествия и тд). Выбор надо осуществлять из нескольких групп, сформированных по уровню подготовленности (здесь могут быть и возрастные группы и все что угодно). Условие — дать пользователю возможность выбрать партнера из одной или нескольких групп одновременно. Другим примером могут служить константы NSRegularExpressionOptions проверки типа данных для класса NSRegularExpression. При подстановке этих констант в методы класса, мы можем записать:

	NSRegularExpressionCaseInsensitive | NSRegularExpressionDotMatchesLineSeparators 

Объединив константы знаком логического «ИЛИ» мы будем уверены, что проверим анализируемую строку на соответствие обоим из заданных условий.

Один из способов реализации подобной задачи — использование списка констант в виде перечисления enum, в котором элементы перечисления представляют собой двоичные числа с одним установленным битом. Сделать это не очень сложно, но сначала немного теории. Вспомним такие битовые операции, как «СДВИГ», «И», «ИЛИ», «НЕ».

Побитовые логические операции с двоичными числами

ЛОГИЧЕСКИЙ БИТОВЫЙ «СДВИГ (SHIFT)»

При логическом сдвиге значение последнего бита по направлению сдвига теряется (копируясь в бит переноса), а первый приобретает нулевое значение.

На картинке изображен логический сдвиг влево на один разряд.

Для полноты картины можно сказать, что при сдвиге влево на один разряд исходное число 01010101 превращается в 10101010. В привычной нам шестнадцатеричный системе (Hex) число 0x55 превращается в 0xAA или в десятичной системе 85 превращается в 170, то есть умножается на 2, что вполне логично.

ПОБИТОВОЕ «ИЛИ (OR)»

Это — бинарная операция, действие которой эквивалентно применению логического ИЛИ к каждой паре битов, которые стоят на одинаковых позициях в двоичных представлениях операндов. Другими словами, если оба соответствующих бита операндов равны 0, двоичный разряд результата равен 0; если же хотя бы один бит из пары равен 1, двоичный разряд результата равен 1.

Визуально это выглядит так:

Применение операции побитового «ИЛИ» к исходным числам 01100110 и 10101010 дает результат 01110110. В шестнадцатеричный системе (Hex) исходные числа 0x66 и 0xAA, результат 0x76 или в десятичной системе исходные числа 102 и 170, результат 118.

ПОБИТОВОЕ «И (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

ссылка на оригинал статьи https://habrahabr.ru/post/279441/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *