Pull to refresh

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

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

	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 и не только…
Создание и компиляция кроссплатформенных (универсальных) библиотек в Xcode
Tags:
Hubs:
Total votes 11: ↑8 and ↓3+5
Comments0

Articles