Learning

Прежде всего нужно понять «Истину», как она представлена в таблице истинности. На самом деле таких таблиц несколько, но для этого урока нам хватит двух  —  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