2013-07-08 8 views
15

Próbuję utworzyć skrypt, aby przejść przez indeks, spojrzeć na każdego numeru strony, a powiedz mi, co rozdział książki, że wejście jest tutaj to przybliżenie tego, co robię.Czy jest możliwe użycie zakresu jako klucza do wartości mieszania w Ruby?

@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) wypluje ciąg, który chcę, ale find_chapter(15) niczego nie zwróci. Czy nie można użyć zakresu jako klucza takiego jak ten?

+0

Można użyć czegoś z funkcjonującym [ 'hash'] (http://ruby-doc.org/core-2.0/Object. html # method-i-hash) jako klucza, a ponieważ jest to zdefiniowane w Object, prawie musisz zrobić wszystko, aby znaleźć obiekt, którego nie można użyć jako klucza. – tadman

Odpowiedz

17

Można użyć szeregu kluczy Hash i można patrzeć klucze bardzo łatwo za pomocą select takiego:

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

Jeśli chcesz tylko nazwę rozdziału, po prostu dodaj .values.first takiego:

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

Najbardziej lubię tę odpowiedź - wydaje się najbardziej idiomatyczna. +1 – jpalm

+7

Wypróbuj '' '@ chapters.detect {| k, v | k === 5} .last''', ponieważ w przeciwieństwie do select, detekcja przestanie się powtarzać w pierwszym meczu. – genkilabs

6

Jasne, po prostu odwrócić porównania

if page_range === number 

Podoba Ci się to

@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 

to działa w ten sposób, ponieważ === metoda na obszarze ma szczególne zachowanie: Range#===. Jeśli umieścisz najpierw number, zostanie wywołana Fixnum#===, która porówna wartości liczbowo. Zakres nie jest liczbą, więc nie pasują.

+0

Mówiąc algorytmicznie, jest to prawdopodobnie dużo wolniejsze niż rozszerzenie zakresów na wiele kluczy o tej samej wartości. – tadman

+0

@tadman: prawdopodobnie. To należy zmierzyć. I oczywiście zależy to od tego, jak duże są twoje zasięgi :) (ekspansja może kosztować dużo RAM). –

+0

Większość ludzi nie ma milionów rozdziałów w książkach, ale twoja uwaga jest ważna. – tadman

2

Jak pokazuje @Sergio Tulentsev, można to zrobić. Najczęstszym sposobem zrobienia tego jest jednak użycie case when. Jest nieco bardziej elastyczny, ponieważ można wykonać kod w klauzuli then i można użyć części else obsługującej wszystko, co nieobsługiwane. Używa tej samej metody === pod maską.

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 
2

Oto zwięzły sposób zwracania tylko wartość pierwszego pasującego klucza:

# 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 } 
1

Znaleźć forum na ten topic. Sugerują

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 

Teraz można go używać jak

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