Menu
CoddyTech

Модуль Comparable

Часть раздела Объектно-ориентированное программирование путешествия по Ruby на Coddy. Урок 29 из 57.

Встроенный модуль Ruby Comparable предоставляет классу операторы <, <=, ==, >, >= и методы between? и clamp. Для этого ему нужен один метод класса: <=>, который возвращает отрицательное число, 0 или положительное число, если объект меньше, равен или больше другого объекта:

class Weight
  include Comparable
  attr_reader :grams

  def initialize(grams)
    @grams = grams
  end

  def <=>(other)
    grams <=> other.grams
  end
end

a = Weight.new(500)
b = Weight.new(1200)
puts a < b
puts a == Weight.new(500)
puts b.between?(a, Weight.new(2000))

Вывод:

true
true
true

У чисел и строк уже есть <=>, поэтому класс обычно делегирует сравнение одному из своих значений. Сравнение массивов выполняется поэлементно, что позволяет сортировать сразу по нескольким полям:

class Person
  include Comparable
  attr_reader :last, :first

  def initialize(first, last)
    @first = first
    @last = last
  end

  def <=>(other)
    [last, first] <=> [other.last, other.first]
  end
end

a = Person.new("Ada", "Lovelace")
b = Person.new("Alan", "Turing")
c = Person.new("Byron", "Lovelace")
puts a < c
puts b > c

Вывод:

true
true

sort, min и max тоже используют <=>, поэтому объекты этого класса можно сортировать без блока. clamp удерживает значение между двумя другими:

class Weight
  include Comparable
  attr_reader :grams

  def initialize(grams)
    @grams = grams
  end

  def <=>(other)
    grams <=> other.grams
  end

  def to_s
    "#{grams}g"
  end
end

list = [Weight.new(900), Weight.new(150), Weight.new(400)]
puts list.sort.join(", ")
puts list.max
puts Weight.new(5000).clamp(Weight.new(0), Weight.new(1000))

Вывод:

150g, 400g, 900g
900g
1000g

<=> должен возвращать nil, если другой объект нельзя сравнить. В таком случае Comparable вызывает ArgumentError для < и возвращает false для ==, вместо того чтобы завершиться с запутанной ошибкой NoMethodError:

def <=>(other)
  return nil unless other.is_a?(Weight)

  grams <=> other.grams
end

Weight.new(1) == 5     # false
Weight.new(1) < 5      # ArgumentError: сравнение Weight с 5 не удалось
challenge icon

Задание

Легко

Завершите класс Weight. Weight.parse(text) считывает значения 500g, 1.2kg или 2lb (1 lb — это 453.6 g) и сохраняет значение в граммах. Подключите Comparable и определите <=> для граммов. to_s возвращает количество граммов, округлённое до целого числа, например 1200g.

Предоставленный код разбирает каждую строку ввода, затем выводит веса в отсортированном порядке, самый тяжёлый и самый лёгкий вес, а также указывает, находится ли первый вес между 1 kg и 2 kg, и выводит первый вес, ограниченный значением не более 1 kg.

Ваш код должен находиться в weight.rb. В main.rb находится предоставленный код ввода/вывода, и его нельзя редактировать.

Попробуйте сами

require_relative 'weight'

# Предоставленный код ввода/вывода: оставьте его без изменений
input = []
while (line = gets)
  input << line.chomp
end
weights = input.map { |line| Weight.parse(line) }
puts "sorted: #{weights.sort.join(", ")}"
puts "heaviest: #{weights.max}"
puts "lightest: #{weights.min}"
puts "first between 1kg and 2kg: #{weights[0].between?(Weight.parse("1kg"), Weight.parse("2kg"))}"
puts "first clamped: #{weights[0].clamp(Weight.parse("0g"), Weight.parse("1kg"))}"
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Ruby