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:
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”
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)”
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) "
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 ">>":
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:
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:
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.
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:
While other numbers with one bit set do not satisfy this condition:
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
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.
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:
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.
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”:
or, substituting the constants:
Many bit constants are similarly constructed, in particular, those mentioned at the beginning of the article NSRegularExpressionOptions:
or NSTextCheckingResult:
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:
Substituting the constant values in this code, we will see how it works:
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:
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.
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:
Thus, for example, writing a function with the form:
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
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