2013-07-08 6 views
15

che sto cercando di creare uno script per passare attraverso un indice, guardare ogni numero di pagina, e me quello capitolo del libro che l'ingresso è a dire Ecco un'approssimazione di ciò che sto facendo:.È possibile utilizzare un intervallo come chiave per un hash in 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) sputerà la stringa desiderata, ma find_chapter(15) non restituisce nulla. Non è possibile usare un intervallo come una chiave come questa?

+0

È possibile utilizzare qualsiasi cosa con un funzionamento [ 'hash'] (http://ruby-doc.org/core-2.0/Object. html # method-i-hash) metodo come chiave, e poiché questo è definito in Object, dovresti quasi fare di tutto per trovare un oggetto che non può essere usato come chiave. – tadman

risposta

17

È possibile utilizzare un intervallo per le chiavi Hash e si può cercare le chiavi molto facilmente usando select come questo:

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

Se si desidera solo il nome del capitolo, basta aggiungere .values.first come questo:

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

Mi piace questa risposta la migliore - sembra la più idiomatica. +1 – jpalm

+7

Prova '' '@ chapters.detect {| k, v | k === 5} .last''' perché, a differenza di select, detect interromperà l'iterazione alla prima corrispondenza. – genkilabs

6

Certo, basta invertire il confronto

if page_range === number 

Ti piace questa

@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 

Funziona in questo modo perché === metodo su Gamma ha un comportamento speciale: Range#===. Se si inserisce prima number, viene chiamato Fixnum#===, che confronta numericamente i valori. L'intervallo non è un numero, quindi non corrispondono.

+0

Algoritmicamente parlando, questo è probabilmente molto più lento del semplice ampliamento degli intervalli in più chiavi che fanno riferimento allo stesso valore. – tadman

+0

@tadman: probabilmente. Questo deve essere misurato. E dipende, ovviamente, da quanto sono grandi le gamme :) (l'espansione potrebbe costarti molta RAM) –

+0

La maggior parte delle persone non ha milioni di capitoli di libri, ma il tuo punto è una cosa importante da notare. – tadman

2

Come dimostra @Sergio Tulentsev, si può fare. Il solito modo di farlo è utilizzando lo case when. È un po 'più flessibile perché è possibile eseguire codice nella clausola then ed è possibile utilizzare una parte else che gestisca tutto non gestito. Utilizza lo stesso metodo === sotto il cofano.

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

Ecco un modo conciso di restituire solo il valore della prima chiave di corrispondenza:

# 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

Trovato un forum su questo topic. Essi suggeriscono

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 

Ora è possibile utilizzarlo come

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