Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to use a range as a key for a hash in Ruby?

Tags:

range

ruby

hash

I'm trying to create a script to go through an index, look at each page number, and tell me what chapter of the book that entry is in. Here's an approximation of what I'm doing:

@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) will spit out the string I want, but find_chapter(15) doesn't return anything. Is it not possible to use a range as a key like this?

like image 275
Tom Avatar asked Jul 08 '13 19:07

Tom


People also ask

What can be a hash key in Ruby?

A Hash is a dictionary-like collection of unique keys and their values. Also called associative arrays, they are similar to Arrays, but where an Array uses integers as its index, a Hash allows you to use any object type. Hashes enumerate their values in the order that the corresponding keys were inserted.

Can a hash have multiple values Ruby?

Each key can only have one value. But the same value can occur more than once inside a Hash, while each key can occur only once.

How do you make hash hash in Ruby?

Ruby hash creation. A hash can be created in two basic ways: with the new keyword or with the hash literal. The first script creates a hash and adds two key-value pairs into the hash object. A hash object is created.

Can a Key have multiple values Ruby?

Multiple Values For One Key Words are unique, but they can have multiple values (definitions) associated with them. You can do this in Ruby!


4 Answers

You can use a range for Hash keys and you can look up keys very easily using select like this:

@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"} 

If you only want the chapter name, just add .values.first like this:

@chapters.select {|chapter| chapter === 9 }.values.first
 #=> "chapter2.xhtml" 
like image 74
MrPizzaFace Avatar answered Oct 18 '22 23:10

MrPizzaFace


Here's a concise way of returning just the value of the first matching key:

# 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 }
like image 45
Noah Thorp Avatar answered Oct 19 '22 00:10

Noah Thorp


Sure, just reverse the comparison

if page_range === number

Like this

@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

It works this way because === method on Range has special behaviour: Range#===. If you place number first, then Fixnum#=== is called, which compares values numerically. Range isn't a number, so they don't match.

like image 7
Sergio Tulentsev Avatar answered Oct 19 '22 01:10

Sergio Tulentsev


As @Sergio Tulentsev demonstrates, it can be done. The usual way to do this however is by using a case when . It is a bit more flexible because you can execute code in the then clause and you can use an else part handling everything unhandled . It uses the same === method under the hood.

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
like image 7
steenslag Avatar answered Oct 19 '22 01:10

steenslag