MSLibrary. Implementing multiple conditions using bitmasks, for iOS and more ...

    We continue to publish materials from the developers of the MSLibrary for iOS library . The topic of this article is not accidental; the problem of choosing several conditions from a given set is not uncommon in our work. The simplest example is the choice of a partner for the game (dates, travels, etc.). The choice must be made from several groups formed according to the level of preparedness (here there may be age groups and anything). The condition is to give the user the opportunity to choose a partner from one or more groups at the same time. Another example is the NSRegularExpressionOptions constants for data type validation for the NSRegularExpression class . When substituting these constants in class methods, we can write:

    	NSRegularExpressionCaseInsensitive | NSRegularExpressionDotMatchesLineSeparators

    By combining the constants with the logical “OR” sign, we will be sure that we will check the line being analyzed for compliance with both of the given conditions.

    One way to accomplish this task is to use a list of constants in the form of an enum enumeration, in which the enumeration elements are binary numbers with one bit set. This is not very difficult to do, but first a bit of theory. Recall such bit operations as “SHIFT”, “AND”, “OR”.

    Bitwise logical operations with binary numbers

    LOGIC BIT “SHIFT”
    During a logical shift, the value of the last bit in the direction of the shift is lost (being copied to the carry bit), and the first acquires a zero value.

    The picture shows a logical shift to the left by one digit.



    For completeness, we can say that when shifting left by one digit, the original number 01010101 turns into 10101010. In the usual hexadecimal system (Hex), the number 0x55 turns into 0xAA or in the decimal system 85 it turns into 170, that is, it multiplies by 2, which is quite is logical.

    BIT “OR (OR)”
    This is a binary operation whose action is equivalent to applying a logical OR to each pair of bits that are at the same positions in the binary representations of the operands. In other words, if both corresponding bits of the operands are 0, the binary bit of the result is 0; if at least one bit from the pair is 1, the binary bit of the result is 1.

    Visually, it looks like this:



    Applying the bitwise OR operation to the source numbers 01100110 and 10101010 gives the result 11101110. In the hexadecimal system (Hex), the initial numbers are 0x66 and 0xAA, the result is 0xEE or in the decimal system the initial numbers are 102 and 170, the result is 238.

    BIT “ AND (AND) "
    This is a binary operation whose action is equivalent to applying a logical “AND” to each pair of bits that are at the same positions in the binary representations of the operands. In other words, if both corresponding bits of the operands are 1, the resulting binary bit is 1; if at least one bit from the pair is 0, the resulting binary bit is 0.



    Applying the bitwise AND operation to the initial numbers 01100110 and 10101010 gives the result 00100010. In the hexadecimal system (Hex), the initial numbers are 0x66 and 0xAA, the result is 0x22 or in the decimal system the initial numbers are 102 and 170, the result is 34.

    Binary numbers with one bit set

    Now let's see what happens when these operations are applied to binary numbers with one bit set.

    DISCHARGE (BIT) SHIFT

    The bitwise shift applied to the number 00000001 will give the following result:



    Bit shift operations are indicated, depending on the direction of shift, by the signs "<<" and ">>":

    	binary Number << n // bit left shift by "n" positions (bits)
    	binaryNumber >> n // bitwise shift to the right by "n" positions (bits)

    The number 00000001 is decimal 1. Therefore, it is customary to denote numbers with one bit set and a bit shift to the left as follows:

    	1 << n // where "n" is the number of positions (digits) by which the shift
    	1 << 0 // 00000001 shift by 0 digits
    	1 << 1 // 00000001 shift by 1 digits
    	1 << 3 // 00000001 shift by 3 digits
    	1 << 5 // 00000001 shift by 5 digits


    BIT LOGIC "OR (OR)"

    Let's take several numbers with one bit set and apply the bitwise logical operation "OR (OR)" to them. It is written like this:

    	1 << 0 | 1 << 3 | 1 << 5

    but it looks like this:



    A remarkable property of such an operation - as a result, a unique value is obtained in this range of numbers.

    The result obtained from applying the bitwise logical “OR” operation to different numbers with one set bit from a given set of numbers is always unique



    BIT LOGIC “AND (AND)”

    The second property of numbers with one bit set is applying the bitwise logical “AND (AND)” operation to any of the numbers included in the “OR (OR)” operation and the result, we get the original number:

    	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 


    While other numbers with one bit set do not satisfy this condition:

    	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 

    For clarity:



    This property of a number obtained by applying the bitwise logical operation “OR (OR)” allows you to use it as a “BIT MASK”.

    BIT MASK
    This is certain data that is used for masking - selecting individual bits or fields from several bits from a binary string or number.

    In other words, the result obtained from applying the bitwise logical “OR” operation to different numbers with one set bit from a given set of numbers can be used as a bitmask.

    Applying the operation of bitwise logical "OR (OR)" to several numbers with one bit set, you can use the result as a bit mask to filter the original numbers from many others.


    Let's move on to practice.

    Enum enumerations with binary numbers with one bit set.

    To determine a set of constants with a given number of specific values, it is convenient to use an enumerated type or enum enumeration. We will write down the listing for a speculative example at the beginning of the article. Suppose we need to define five user groups. It is written like this:

    	enum Groups {
    		group_0 = 0,
    		group_1 = 1
    		group_2 = 2,
    		group_3 = 3,
    		group_4 = 4,
    	};

    We can use “Groups” to select a suitable group of users, but we cannot combine these groups, that is, we cannot select users from the groups “group_2” and “group_3” and organize the filtering of all users by these parameters. We can calculate the control number by performing the operation (2 + 3) = 5, but it will not be unique, the groups “group_1” and “group_4” will give the same result: (1 + 4) = 5. We modify the expression by specifying the number as the value with one bit set and using a bitwise left shift.

    	enum Groups {
    		group_0 = 1 << 0,
    		group_1 = 1 << 1,
    		group_2 = 1 << 2,
    		group_3 = 1 << 3,
    		group_4 = 1 << 4
    	};

    In this case, we can easily create a “BIT MASK” by applying the bitwise “OR” operations to the selected parameters. Let's say we chose the same “group_2” and “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

    or, substituting the constants:

    	(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


    Many bit constants are similarly constructed, in particular, those mentioned at the beginning of the article 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)
    }; 

    or 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
    }; 

    Back to our user group example. We created a bitmask, now we need to write code to use it. If there are two approaches for solving this problem, the first - using the "if () {}" operators and the second - which uses a bunch of "for () {}" and "switch () {}" operators, consider both.

    Using the if () {} operator

    This approach is fairly straightforward. The code below does not need special comments:

    	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) {
    		// действие, совершаемое в случае соответствия константы и маски
    	}
    

    Substituting the constant values ​​in this code, we will see how it works:

    	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) {
    		// действие, совершаемое в случае соответствия константы и маски
    	}
    


    Thus, certain actions are performed in all cases of constant and mask matching, in our example, for users from groups "group_2" and "group_3".

    Using the for () {} and switch () {}

    operators The second approach is to use a bunch of for () {} and switch () {} operators. The advantage of this option is that if for different constants of “Groups” it is necessary to perform identical actions, for example, use the same functions that differ only in certain variables, this approach allows you to create a more compact and elegant code:

     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"
                	}
    	}
    


    Many methods and functions of our MSLibrary for iOS library are organized by the same principle . For example, the function: msfDDstringCheckingStyle () takes the values ​​“YES” or “NO” depending on whether the specified conditions are fulfilled in the analyzed string.

     BOOL msfDDstringCheckingStyle(NSString *string, tMSstringCheckingStyle stringCheckingStyle, BOOL allConditionsIsRequired, NSInteger minLengthOfString)

    where
    string is the string to be analyzed
    stringCheckingStyle is a constant that imposes certain restrictions
    allConditionsIsRequired is a flag, if it has the value “YES”, fulfillment of the conditions defined by all the stringCheckingStyle constants is mandatory, if it has the value “NO”, any one or several specified conditions
    minLengthOfString - minimum string length

    Constants "stringCheckingStyle" are specified as follows:

     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;
    

    Thus, for example, writing a function with the form:

    	msfDDstringCheckingStyle(NSString *string, tMSstringCheckingStyle kMSstringCheckingStyle_digits | kMSstringCheckingStyle_englishLetters, BOOL YES, NSInteger 8)

    we will get a positive result only if the string "string" will have at least 8 characters and it will necessarily contain the letters of the English alphabet and numbers, which is useful, for example, when checking new passwords.

    As you can see, the question is solved in just one line of code.



    We hope that the material was useful to you, the MSLibrary for iOS team

    Other articles: Capturing and verifying phone numbers using regular expressions, for iOS and more ... Part 1 Capturing and verifying phone numbers using regular expressions, for iOS and more ... Part 2 SIMPLY: remove unnecessary characters from the string, for iOS and not only ... Creating and compiling cross-platform (universal) libraries in Xcode




    Also popular now: