Можно ли использовать диапазон в качестве ключа для хэша в Ruby?

Я пытаюсь создать script, чтобы просмотреть индекс, посмотреть номер каждой страницы и рассказать мне, в какой главе книги находится эта запись. Здесь приближается то, что я делаю:

@chapters = {
  1 => "introduction.xhtml",
  2..5 => "chapter1.xhtml",
  6..10 => "chapter2.xhtml",
  11..18 => "chapter3.xhtml",
  19..30 => "chapter4.xhtml" }

def find_chapter(number)
  @chapters.each do |page_range, chapter_name|
    if number === page_range
      puts "<a href=\"" + chapter_name + "\page" + number.to_s + "\">" + number.to_s + </a>"
    end
  end
end

find_chapter(1) выплюнет нужную строку, но find_chapter(15) ничего не возвращает. Невозможно использовать диапазон в качестве такого ключа?

Ответы

Ответ 1

Вы можете использовать диапазон для клавиш Hash, и вы можете легко искать клавиши с помощью select следующим образом:

@chapters = { 1 => "introduction.xhtml", 2..5 => "chapter1.xhtml", 
              6..10 => "chapter2.xhtml", 11..18 => "chapter3.xhtml",                                         
              19..30 => "chapter4.xhtml" } 

@chapters.select {|chapter| chapter === 5 }
 #=> {2..5=>"chapter1.xhtml"} 

Если вам нужно только имя главы, просто добавьте .values.first следующим образом:

@chapters.select {|chapter| chapter === 9 }.values.first
 #=> "chapter2.xhtml" 

Ответ 2

Конечно, просто измените сравнение

if page_range === number

Подобно этому

@chapters = {
  1 => "introduction.xhtml",
  2..5 => "chapter1.xhtml",
  6..10 => "chapter2.xhtml",
  11..18 => "chapter3.xhtml",
  19..30 => "chapter4.xhtml" }

def find_chapter(number)
  @chapters.each do |page_range, chapter_name|
    if page_range === number
      puts chapter_name
    end
  end
end

find_chapter(1)
find_chapter(15)
# >> introduction.xhtml
# >> chapter3.xhtml

Это работает так, потому что метод === на Range имеет особое поведение: Range # ===. Если сначала поместить number, тогда вызывается Fixnum#===, который сравнивает значения численно. Диапазон не является числом, поэтому они не совпадают.

Ответ 3

Как демонстрирует @Sergio Tulentsev, это можно сделать. Однако обычный способ сделать это - использовать case when. Это немного более гибко, потому что вы можете выполнить код в предложении then, и вы можете использовать часть else, обрабатывающую все необработанные. Он использует тот же метод === под капотом.

def find_chapter(number)
  title = case number
    when 1      then "introduction.xhtml"
    when 2..5   then "chapter1.xhtml"
    when 6..10  then "chapter2.xhtml"
    when 11..18 then "chapter3.xhtml"
    when 19..30 then "chapter4.xhtml"
    else "chapter unknown"
  end
  #optionally: do something with title
end

Ответ 4

Вот краткая возможность возврата только значения первого совпадающего ключа:

# setup
i = 17; 
hash = { 1..10 => :a, 11..20 => :b, 21..30 => :c }; 

# find key
hash.find { |k, v| break v if k.cover? i }

Ответ 5

Найден форум на этом topic. Они предлагают

class RangedHash
  def initialize(hash)
    @ranges = hash
  end

  def [](key)
    @ranges.each do |range, value|
      return value if range.include?(key)
    end
    nil
  end
end

Теперь вы можете использовать его как

ranges = RangedHash.new(
  1..10 => 'low',
  21..30 => 'medium',
  41..50 => 'high'
)
ranges[5]  #=> "low"
ranges[15] #=> nil
ranges[25] #=> "medium"