Самостоятельная работа 2: булевские операции

Кувшинов Д.Р.

2015


Общее оглавление


7 баллов

Цели и критерии

Цель: изучить булевские (“логические”) операции на примере вычисления индикаторов множеств на плоскости, соответствие булевских и теоретико-множественных операций.

Критерии полноты

  1. Индикатор множества, изображённого на иллюстрации в назначенном варианте, должен быть реализован в виде функции, принимающей координаты точки и возвращающей булевское значение.
  2. Программа должна запрашивать у пользователя координаты точки и выводить признак попадания указанной точки в заданную фигуру.
  3. Программа должна позволять проверить на попадание произвольное количество точек без перезапуска.


Основные определения

Скобка Айверсона — обозначение с помощью заключения логического утверждения в квадратные скобки численного значения этого утверждения (0 для лжи, 1 для истины). Например, sgn(x) = [x > 0] – [x < 0].

Индикатор множества S или характеристическая функция множества S — это функция вида 1S: XB, где X — некое надмножество множества S (т. е. SX), а B = {0, 1}, такая, что 1S(x) = [xS].

Индикатор обладает рядом интересных свойств. Пусть AX и BX, тогда следующие утверждения верны для любого xX.

  1. Дополнение множества соответствует логическому отрицанию (логическое “не” ¬) индикатора: 1X\A(x) = 1 – 1A(x) = ¬1A(x).
  2. Пересечение множеств ∩ соответствует конъюнкции (логическое “и” ∧) индикаторов: 1AB(x) = 1A(x) ∧ 1B(x) = 1A(x1B(x) = min(1A(x), 1B(x)).
  3. Разность множеств \ (пересечение с дополнением): 1B\A(x) = 1B(x) ∧ ¬1A(x).
  4. Объединение множеств ∪ соответствует дизъюнкции (логическое “или” ∨) индикаторов: 1AB(x) = 1A(x) ∨ 1B(x) = (1A(x) + 1B(x)) – 1A(x1B(x) = max(1A(x), 1B(x)).
  5. Симметрическая разность ∆ (разность объединения и пересечения) соответствует логическому исключающему “или” (также известному как “xor” exclusive or и “сложение по модулю два” ⊕) индикаторов: 1AB(x) = 1A(x) ⊕ 1B(x) = [1A(x) ≠ 1B(x)].

В C++ встроен тип bool, множество значений которого состоит из логических констант true (“истина”, численно равна 1) и false (“ложь”, численно равна 0). Пример реализации булевской функции “и-не”:

bool nand(bool a, bool b)
{
  return !(a && b); // не (a и b)
}


Операторы сравнения
Отношение C++
= “равно” ==
!=
< <
> >
<=
>=


Соответствие операций над множествами и булевскими величинами
Множества Логика Логика — C++
пространство 1 true
0 false
дополнение ¬ !
&&
||
\ ∧¬ &&!
!=
< <
≤, → <=


Проверка на равенство логических величин совпадает с операцией “эквиваленция”, на неравенство — с операцией “исключающее или”. В C++ нет таких встроенных логических операций, поэтому может использоваться простое сравнение на равенство == или неравенство != при соблюдении важного условия: их операнды должны быть результатами вычисления логических выражений либо логическими константами true и false.

Сравнения вида что-то == true и что-то == false избыточны.

Почему условие выше названо важным? Дело в том, что bool — фактически целочисленный тип с размером представления не меньше одного байта (а может и больше), в то время как для значений 0 и 1 достаточно одного бита. Допускается приведение к bool целочисленных значений, при этом ноль трактуется как ложь, а все прочие значения как истина. Но переданное в булевскую функцию число может и дальше неявно интерпретироваться как число (особенно, если включены оптимизации, применяемые компилятором).

bool bad_xor(bool a, bool b)
{
  return a != b; // хм?
}

int main()
{
  cout << bad_xor(1, 2); // может быть как 1, так и 0
  cout << bad_xor(true, true); // обязательно 0
}

Для исправления ситуации удобно использовать оператор отрицания, для которого гарантируется, что его результат равен либо 0, либо 1. Та же гарантия предоставляется для всех встроенных операторов, возвращающих булевское значение (“и”, “или”, сравнения).

bool logic_xor(bool a, bool b)
{
  // Выглядит странно, но работает правильно.
  return !a != !b;
}

Данная формула короче логических аналогов (a && !b) || (!a && b) и (a || b) && !(a && b).

У изучающих язык C нередко возникает вопрос, почему в нём нет логического оператора xor ^^ в то время как есть поразрядный оператор ^. Короткий ответ: потому что он не может быть вычислен по “короткой схеме” как “и” и “или”. См. также развёрнутый ответ Д.Ричи.


Уравнение прямой на плоскости имеет вид ax + by + c = 0, где (x, y) ∈ ℝ2 – координаты некоторой точки на плоскости, (a, b, c) ∈ (ℝ2 \ 0) × ℝ — коэффициенты уравнения. Если интерпретировать запись уравнения как логическое выражение, вычисляющее [ax + by + c = 0], то получим определение индикатора прямой.

Неравенство полуплоскости легко получить из уравнения прямой, заменив знак равенства на тот или иной знак неравенства. Если точки прямой, ограничивающей полуплоскость, включаются в полуплоскость, то неравенство нестрогое, в противном случае — строгое. Неравенство полуплоскости позволяет легко задать индикатор полуплоскости.

Упражнение. Попробуйте изобразить четыре полуплоскости, заданные неравенствами:


Пример выполнения задания

Дано

Заданная фигура
Заданная фигура

Решение

Фигура на изображении является пересечением полуплоскости (x ≥ –4) и объединения следующих фигур:

Удобно определить отдельные функции-индикаторы для множеств “круг с заданными радиусом и координатами центра” и “прямоугольник со сторонами, параллельными осям координат, и заданными координатами углов”.

// Проверить попадание в круг.
bool in_circle(float x, float y, // координаты точки
  float cx, float cy, float r)   // координаты центра круга и его радиус
{
  // Константы -- после инициализации значения не изменяются.
  const float dx = x - cx,
              dy = y - cy;

  return dx * dx + dy * dy <= r * r;
}

// Проверить попадание в прямоугольник.
bool in_rectangle(float x, float y,
  float left, float right, float bottom, float top)
  // координаты левой, правой, нижней и верхней граней
{
  return left <= x && x <= right // && -- "и"
    && bottom <= y && y <= top;
}

Теперь не составит труда написать индикаторную функцию заданной фигуры.

// Проверить попадание в заданную фигуру.
bool in_figure(float x, float y)
{
  // фигура может быть представлена как пересечение полуплоскости и
  // объединения трёх фигур: двух прямоугольников и сегмента круга
  return (in_rectangle(x, y,  2.0,  4.0, -5.0, 5.0)
       || in_rectangle(x, y, -4.0, -2.0, -5.0, 5.0) // || -- "или"
       || in_circle(x, y, -2.0, 0.0, 5.0)) && x >= -4.0;
}

Функция main итоговой программы, удовлетворяющей критериям полноты, выглядит следующим образом:

int main()
{
  cout << "Enter a sequence of coordinates x, y: ";
  // Цикл ввода пар значений x, y до первой ошибки ввода.
  for (double x = 0, y = 0; cin >> x >> y;)
  {
    cout << "(x, y) within the figure == " << in_figure(x, y) << endl;
  }
  return 0;
}

C++, HTML

Для выполнения данного задания можно взять исходный код примера и заменить в нём функцию-индикатор фигуры-примера на свою функцию-индикатор.

Этот же пример с визуализацией функции-индикатора с помощью OpenGL/GLSL.


Варианты

1

Фигура 1
Фигура 1

2

Фигура 2
Фигура 2

3

Фигура 3
Фигура 3

4

Фигура 4
Фигура 4

5

Фигура 5
Фигура 5

6

Фигура 6
Фигура 6

7

Фигура 7
Фигура 7

8

Фигура 8
Фигура 8

9

Фигура 9
Фигура 9

10

Фигура 10
Фигура 10

Общее оглавление

Кувшинов Д.Р. © 2015