Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Рациональные числа Java: Составные данные

Абстракция

Пары отлично подходят для представления рациональных чисел. Первый элемент пары может выступать числителем, а второй элемент — знаменателем.

a / b → (a, b)

Мы увидели, как пары могут использоваться для представления разных структур, а сочетания таких представлений — создавать новые структуры.

Пара — структура данных для представления рациональных чисел

Рациональные числа

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

import static Rational.denom;
import static Rational.make;
import static Rational.numer;

var rat = make(5, 4);
numer(rat); // 5
denom(rat); // 4

Конструктор make принимает два числа. Селектор numer ответственен за получение числителя, denum — знаменателя.

На рациональных числах определены различные операции, например, сложение рациональных чисел, умножение — всё это происходит по определённым правилам:

n1/d1 + n2/d2 = (n1 * d2 + n2 * d1) / (d1* d2)

n1/d1 * n2/d2 = (n1 * n2) / (d1 * d2)

А по такой формуле работает функция isEqual, которая проверяет равенство дробей:

n1/d1 = n2/d2, *if* n1 * d2 = n2 * d1

Абстракция позволяет нам делать некоторые интересные вещи так, чтобы остальная часть программы об этом не знала. Например, нормализация дроби:

add(make(1, 10), make(4, 10)); // 5/10

Сложив два числа из примера, мы получим 5/10, что является одним из представлений числа 1/2 или 10/20 (обычно нормализуют в меньшую сторону). Когда мы говорим про работу с абстракцией, у нас есть несколько способов провести нормализацию числа. Например, это можно делать при вызове конструктора:

make(5, 10); // 1/2

Тогда нормализация будет производиться один раз при создании рационального числа. Другой способ — делать нормализацию при вызове селекторов:

numer(make(5, 10)); // 1
denom(make(5, 10)); // 2

При этом снаружи будет совершенно всё равно, какой способ мы используем. Очевидно, что в данном конкретном случае использование первого способа предпочтительней с точки зрения производительности, потому что селекторы будут вызываться намного чаще конструктора, и производить вычисления каждый раз (а они могут быть довольно объёмными) просто неэффективно. Важно понимать на концептуальном уровне, что способ не один, и его выбор зависит от требований к конкретной системе. Поскольку мы построили барьер абстракции, и манипуляции с рациональными числами производятся только через конструктор и селекторы, то оптимизацию можно спокойно отложить на более поздний срок.

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

make(-1, 2); // - 1/2

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

При этом есть различные пограничные случаи, например:

add(make(-1, -4), make(2, 4)); // 3/4

В примере выше и числитель и знаменатель в первом рациональном числе — отрицательные числа. В таком случае два знака - у дроби должны уходить. А значит, функция make должна знать об этом. Само по себе это не заработает, и вместо правильного результата могут появляться совершенно непредсказуемые эффекты.

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


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»
Изображение Тото

Задавайте вопросы, если хотите обсудить теорию или упражнения. Команда поддержки Хекслета и опытные участники сообщества помогут найти ответы и решить задачу