Прежде всего нужно понять «Истину», как она представлена в таблице истинности. На самом деле таких таблиц несколько, но для этого урока нам хватит двух — AND
и OR
. Начнём.
AND
Обозначается как &
. В этом случае сравнение двух битов, A и B, даст результат 1 только тогда, когда оба A и B равны 1
.
С помощью приведённой выше таблицы легко определить результат побитовой операции AND
между двумя целыми числами. Например, чтобы найти результат 12 & 4
, преобразуйте оба числа в их двоичные представления:
12 = 1100
4 = 100
Так-так. 12 имеет четыре бита, а 4 всего три бита. В таком случае можно сделать их одинаковой длины, добавив 0
туда, где меньше битов. 4 = 0100
.
Теперь, когда числа одинаковой длинны, мы можем произвести операцию:
1100 &0100 =
0100
Начиная с самого правого бита каждого числа, сверяясь с таблицей выше, найдите все значения. Итак, у нас получается 0&0=0
, 0&0=0
, 1&1=1
и 1&0=0
. Результат 0100
, т.е. 4, значит 12&4 = 4
.
OR
Обозначается как |
. Здесь, сравнение двух битов, A и B, даст результат 1
, если хотя бы один бит (A или B) равен 1
.
С помощью этой таблицы можно определить результат побитовой операции OR
между двумя целыми числами.
Например, чтобы найти результат 13|3
, снова преобразуем целые числа в двоичное представление:
13 = 1101
3 = 11
Добавляем нули: 3 = 0011
.
Вычисляем OR
:
1101 |0011 =1111
Сопоставляем каждый бит, начиная справа, с таблицей выше, чтобы найти соответствующее значение. Итак, у нас получается 1|1=1
, 0|1=1
, 1|0=1
и 1|0=1
. Результат 1111
, т.е. 15, значит 13|3=15
.
Чем это может быть полезно?
Представьте, что вам нужно написать функцию, которая принимает строку и возвращает строку декоратор в окружении какого-либо из следующих тегов: <i>
, <b>
, и <u>
.
Вариант 1
Мы можем написать функцию, которая учитывает все теги в своей подписи. Тогда у нас получится что-то вроде этого:
function decorateString1(str, includeITag, includeBTag, includeUTag) {
let result = str;
if (includeITag) {
result = '<i>' + result + '</i>';
}
if (includeBTag) {
result = '<b>' + result + '</b>';
}
if (includeUTag) {
result = '<u>' + result + '</u>';
}
return result;
}
console.log(decorateString("Hello", true))
/* <i>Hello</i> */
console.log(decorateString("Hello", true, true))
/* <b><i>Hello</i></b> */
console.log(decorateString("Hello", true, true, true))
/* <u><b><i>Hello</i></b></u> */
Данная функция работает хорошо. Сложность этого варианта заключается в том, что вы должны запомнить расположение каждого тега при передаче аргументов в функцию.
Кроме того, предположим, что вы просто хотите подчеркнуть текст. Вам придётся сделать что-то вроде этого: decorateString("Hello", false, false, true)
. Любой, кто будет читать этот код (кроме вас), должен будет найти функцию и посмотреть, что в ней, чтобы узнать, что является false
, а что true
.
Вариант 2
Вообще-то вам не нужно писать функцию таким способом в Javascript. Вы можете сделать второй аргумент объектом и деструктурировать его, чтобы получить доступ к значениям и работать с ними. Вот так:
function decorateString(str, style) {
const {includeITag, includeBTag, includeUTag} = style || {};
let result = str;
if (includeITag) {
result = '<i>' + result + '</i>';
}
if (includeBTag) {
result = '<b>' + result + '</b>';
}
if (includeUTag) {
result = '<u>' + result + '</u>';
}
return result;
}
console.log(decorateString("Hello", {includeUTag: true}));
/* <u>Hello</u> */
Так намного лучше, так как мне не нужно запоминать порядок тегов, а только указывать те, которые я хочу. Но я не использую Javascript. Есть ещё варианты?
Вариант 3
Вариант есть. Давайте представим, что бит представляет собой флаг. Когда бит равен 1
, флаг поднимается, когда бит равен 0
, флаг опускается. Теперь у каждого тега будет флаг в определенной позиции. Мы можем определить каждый из тегов следующим образом (в двоичном формате):
includeITag = 001 // 1
includeBTag = 010 // 2
includeUTag = 100 // 4
Вызов функции будет выглядеть так:
- Чтобы включить только тег I:
decorateString("Hello", 1)
- Чтобы включить только тег B:
decorateString("Hello", 2)
- Чтобы включить только тег U:
decorateString("Hello", 4)
Неплохо, но мне придётся запоминать эти цифры. Верно?
На самом деле нет. Эти значения будут определены как константы в классе/файле, который содержит эту функцию, поэтому вам не нужно их запоминать.
Но нет никакого способа использовать два тега вместе. Чтобы строка включала теги I и B, нам нужно установить два флага, т.е. 011
. А чтобы включить три тега, нам нужно 111
.
Т.е. нам нужно определять значения для всех возможных комбинаций тегов? Нет. Вы создаёте только те, которые вам нужны, и генерируете комбинации. В этом нам пригодится оператор |
.
includeITag | // 001
includeBTag // 010
// 011
Теперь мы можем вызывать функцию так:
Чтобы включить только I и B теги: decorateString("Hello", includeITag | includeBTag)
Чтобы включить три тега: decorateString("Hello", includeITag | includeBTag | includeUTag)
Использовать можно в любом порядке.
Как же тогда извлечь эти значения из входных данных? Проверяя, установлен ли флаг! В этом нам поможет оператор &
.
Чтобы проверить, установлен ли бит в индекс i
, нужно проверить значение индекса 2^i
через AND
. Если результат не равен 0
, то бит установлен, иначе бит не установлен.
Например, нужно проверить 1101
, установлен ли бит с индексом 1
: 2¹=2
. Получается:
1101 &
0010 =
0000
Так как это 0
, бит в индексе 1
не установлен. А если так же проверить 1011
:
1011 &
0010 =
0010
Так как результат не равен 0
, значит бит в индексе 1
установлен. Теперь мы можем создать такую функцию:
const includeITag = 0b001; // 1
const includeBTag = 0b010; // 2
const includeUTag = 0b100; // 4
function decorateString(str, styleFlag) {
let result = str;
if ((styleFlag & includeITag) !== 0) {
result = '<i>' + result + '</i>';
}
if ((styleFlag & includeBTag) !== 0) {
result = '<b>' + result + '</b>';
}
if ((styleFlag & includeUTag) !== 0) {
result = '<u>' + result + '</u>';
}
return result;
}
console.log(decorateString("Hello", includeBTag | includeUTag));
/* <u><b>Hello</b></u> */
console.log(decorateString("Hello", includeUTag));
/* <u>Hello</u> */
Это можно реализовать в любом языке программирования, без деструктуризации или передачи длинного списка параметров. Пример использования такого подхода — установка флагов при запуске нового Activity
в Android:
myIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION|Intent.FLAG_ACTIVITY_CLEAR_TOP);
Перевод статьи Ekundayo Blessing Funminiyi: Bit Manipulation — Playing With the Truth