SICP. Интервалы

В прошлой статье мы написали крохотную библиотеку для работы с обыкновенными дробями. Теперь напишем ещё одну для работы с интервалами. В вычислениях будут десятичные дроби, поэтому мы как бы дополним прошлую библиотеку.

Интервалы

Интервал — последовательность чисел от нижней до верхней границы с каким-то шагом, например интервал от 3 до 6 с шагом 1 выглядит так: [3, 4, 5, 6].
Интервальная арифметика часто используется в электрике, например чтобы вычислить сопротивление резистора или их соединений, обычно там важны только два числа — нижняя и верхняя границы интервала.

Размер интервала получается из разности: большее − меньшее

Как мы сказали выше, для описания интервала нужны только два значения, для этого отлично подходит пара из нашей библиотеки. Поэтому абстракция крутая штука — из одного файла, можно сделать разные сценарии с парами чисел: дроби, интервалы, координаты, комплексные числа, список, прокси в виде айпи и порта, и всё другое что можно представить в виде пары значений.

Сначала пишем конструктор для создания интервала и два селектора, они будут доставать нижнюю и верхнюю границы, потом проверим чтобы всё это работало.

from cmpd_data import pair


# Проверяем какое из чисел больше
# Это нужно чтобы первым элементом в паре всегда была нижняя граница
# Вторым элементом – верхняя
def make_interval(a, b):
    if a < b:
        return pair.cons(a, b)
    return pair.cons(b, a)


# Достаём нижнюю границу
def lower_bound(interval):
    return pair.car(interval)


# Достаём верхнюю границу
def upper_bound(interval):
    return pair.cdr(interval)


# Проверяем, что условие в конструкторе работает как нужно
interval_a = make_interval(2, 4)
print(lower_bound(interval_a), upper_bound(interval_a))

interval_b = make_interval(4, 2)
print(lower_bound(interval_b), upper_bound(interval_b))

Запускаем код и видим, что конструктор правильно расставляет границы.

$ python3 interval.py
2 4
2 4

Операции

Допишем в файл пять основных операций: сложение, вычитание, умножение, деление и вывод. Ещё напишем вычисление центрального числа и радиуса интервала, это нужно чтобы красиво выводить результаты.

Этот файл можно допиливать до бесконечности: добавить новые функции или улучшить старые, но это нам пока не нужно. Проверим как код справляется с простой задачей в электрике.

Проверяем на двух резисторах
Есть два резистора с сопротивлением в 4 и 6 Ом, погрешность каждого — 5%. Подключаем их параллельно и считаем общее сопротивление цепи, вот формула: \(\frac {R_1 * R_2} {R_1 + R_2}\), R1 и R2 — интервал сопротивления резистора с погрешностью.

Запускаем код и получаем общее сопротивление цепи в 2.29 Ом и погрешность в 23%.

$ python3 resistor.py
2.29±0.23

Зачем делить код на разные файлы

У нас есть библиотека для работы с обыкновенными дробями и интервалами, дробь и интервал мы создали через пару, можно сделать ещё всего и побольше. Мы можем всё это намешать в один файл и создание дробей и интервалов выглядело бы так

from cmpd_data import pair

x = cons(4, 5)  # Создаём обыкновенную дробь
a = cons(3.56, 5.65)  # Создаём интервал

Это грязно, так делать не нужно. Выглядит непонятно и запутанно, а в коде это ещё хуже.
Чтобы такого не было, мы делим код на логические файлы и отделяем реализацию понятия от его использования, это и есть абстракция данных. Код становится понятным даже без комментариев и его можно легко изменять: не нужно вылавливать в коде все создания дробей и связанные с ними операции, достаточно изменить код в конструкторе make_rational и его селекторах.

x = make_rational(4, 5)
a = make_interval(3.56, 5.65)
Поделиться
Отправить
Запинить
 32   25 дн   sicp
Популярное