⬆️ ⬇️

MSLibrary. Implementing multiple selection of conditions using bitmasks, for iOS and not only ...

We continue to publish materials from the developers of the library MSLibrary for iOS. 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 choosing a partner for a game (dating, traveling, etc.). The choice should be made from several groups formed according to the level of preparedness (there may be age groups and anything else). The condition is to allow the user to select a partner from one or several groups at the same time. Another example is the NSRegularExpressionOptions data type checking constants for the NSRegularExpression class. When substituting these constants in the class methods, we can write:



  NSRegularExpressionCaseInsensitive |  NSRegularExpressionDotMatchesLineSeparators 


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



One way to accomplish such a task is to use a list of constants in the form of an enum enumeration, in which the elements of the enumeration 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

')

LOGICAL BIT "SHIFT)

In the case of a logical shift, the value of the last bit in the direction of the shift is lost (copied to the carry bit), and the first one becomes zero.


The picture shows a logical left shift by one digit.







For completeness, we can say that when shifting left by one digit, the original number 01010101 turns into 10101010. In the hexadecimal system (Hex) we are used to, the number 0x55 turns into 0xAA or decimally 85 turns into 170, that is, multiplied by 2, which is quite is logical.



FAST "OR (OR)"

This is a binary operation, the operation of which 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 the corresponding bits of the operands are 0, the binary bit of the result is 0; if at least one bit of the pair is 1, the binary bit of the result is 1.


Visually, it looks like this:







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



KILLING "AND (AND)"

This is a binary operation, the action of which 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 of the pair is 0, the resulting binary bit is 0.






Applying the bitwise AND operation to the original 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 original 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.



PORRIFIED (AFTER) SHIFT



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







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



  binaryNumber << n // bit left shift to "n" positions (digits)
	 binaryNumber >> n // bit shift to the right by "n" positions (digits) 


The number 00000001 is decimal 1. Therefore, numbers with one bit set and a bit left shift are usually denoted as follows:



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




PICTURAL LOGIC "OR (OR)"



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



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


and it looks like this:







A wonderful property of such an operation is the result: a unique value 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





FASTING LOGICAL "AND (AND)”



The second property of numbers with one set bit - applying the bitwise logical “AND (AND)” operation to any of the numbers included in the “OR (OR)” operation and the result obtained, 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 the number obtained as a result of applying the bitwise logical "OR (OR)" operation allows using it as a "BIT MASK".



BIT MASK

This is the specific data that is used for masking — the selection of individual bits or fields from several bits from a binary string or number.


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



By applying the bitwise logical "OR (OR)" operation 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 go to practice.



Enum enumerations with binary numbers with one bit set



To define a set of constants, with a specified number of specific values, it is convenient to use an enumerated type or enum enum. We write the enumeration for the speculative example given at the beginning of the article. Suppose we need to define five user groups. This 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 the appropriate 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 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. Modify the expression, specifying the numbers as 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 bitwise OR operations to the selected parameters. Suppose 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 by substituting 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 arranged, in particular the NSRegularExpressionOptions mentioned at the beginning of the article:



  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 (eg flight) info detection }; 


Let's return to our example with user groups. We created a bitmask, now we need to write code to use it. If there are two approaches to solving this problem, the first one is using the if () {} operators and the second one, in which a bunch of for () {} and switch () {} operators are used, consider both.



Using the if () {} operator



This approach is fairly straightforward. The following code 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 values ​​of the constants 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 matching constants and masks, in our example, for users from the groups "group_2" and "group_3".



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



The second approach is to use a bunch of operators "for () {}" and "switch () {}". The advantage of this option is that if for different “Groups” constants it is necessary to perform identical actions, for example, to use the same functions that differ only in certain variables, then 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 of the methods and functions of our MSLibrary for iOS library are organized in the same way. For example, the function: msfDDstringCheckingStyle () takes the values ​​“YES” or “NO” depending on whether the specified conditions are met in the analyzed string.



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


Where

string - analyzed string

stringCheckingStyle - constants that impose some restrictions

allConditionsIsRequired - the flag, in case it has the value “YES”, the conditions defined by all constants “stringCheckingStyle” are mandatory, if it has the value “NO”, any one or several specified conditions can be executed

minLengthOfString - minimum string length



Constants "stringCheckingStyle" are given 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” has at least 8 characters and it will necessarily contain letters of the English alphabet and numbers, which is useful, for example, when checking new passwords.



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






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



Other articles:

Capture and verify phone numbers using regular expressions, for iOS and not only ... Part 1

Capturing and verifying phone numbers using regular expressions, for iOS and not only ... Part 2

SIMPLE: remove unnecessary characters from the string, for iOS and not only ...

Creating and compiling cross-platform (universal) libraries in Xcode

Source: https://habr.com/ru/post/279441/



All Articles