2012-05-21 15 views
5

Una delle cose che di solito vengo catturato in ruby ​​sono i pattern di ricorsione. Ad esempio, supponiamo di avere un array e che possa contenere array come elementi a una profondità illimitata. Così, per esempio:Ruby: schemi di ricorsione standard

my_array = [1, [2, 3, [4, 5, [6, 7]]]] 

mi piacerebbe creare un metodo che può appiattire l'array in [1, 2, 3, 4, 5, 6, 7].

Sono a conoscenza del fatto che lo .flatten farebbe il lavoro, ma questo problema è da intendersi come un esempio di problemi di ricorsività a cui mi imbatto regolarmente - e come tale sto cercando di trovare una soluzione più riutilizzabile.

In breve, immagino ci sia un modello standard per questo genere di cose, ma non riesco a trovare nulla di particolarmente elegante. Tutte le idee apprezzato

risposta

5

ricorsione è un metodo, non dipende dalla lingua. Si scrive l'algoritmo con due tipi di casi in mente: quelli che chiamano di nuovo la funzione (casi di ricorsione) e quelli che lo infrangono (casi base). Ad esempio, per eseguire un appiattimento ricorsivo in Ruby:

class Array 
    def deep_flatten 
    flat_map do |item| 
     if item.is_a?(Array) 
     item.deep_flatten 
     else 
     [item] 
     end 
    end 
    end 
end 

[[[1]], [2, 3], [4, 5, [[6]], 7]].deep_flatten 
#=> [1, 2, 3, 4, 5, 6, 7] 

Questo aiuto? in ogni caso, uno schema utile mostrato qui è che quando si utilizza la recusione su array, di solito è necessario flat_map (l'alternativa funzionale a each + concat/push).

+0

Grazie per la risposta. Inizialmente provavo con una funzione esterna a una classe, che non chiamava in modo ricorsivo item.my_flatten ... ma questo funziona. Saluti – PlankTon

+1

@PlankTon. Scrivere my_flatten come una funzione richiede solo alcune piccole modifiche al codice sopra. Ma essendo un linguaggio OOP e 'my_flatten' un algoritmo generico, ha senso averlo in Array. – tokland

4

Beh, se si conosce un po 'di C, è sufficiente visitare la documentazione e fare clic sulla funzione di rubino per ottenere il sorgente C ed è tutto lì ..

http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-flatten 

E per questo caso, ecco un'implementazione rubino

def flatten values, level=-1 
    flat = [] 
    values.each do |value| 
    if level != 0 && value.kind_of?(Array) 
     flat.concat(flatten(value, level-1)) 
    else 
     flat << value 
    end 
    end 
    flat 
end 

p flatten [1, [2, 3, [4, 5, [6, 7]]]] 
#=> [1, 2, 3, 4, 5, 6, 7] 
+0

Ahh - concat. Stavo provando a chiamare ricorsivamente la funzione stessa, ma lo farò. Saluti – PlankTon

2

Ecco un esempio di un appiattimento che è scritto in uno stile ricorsivo della coda.

class Array 

    # Monkeypatching the flatten class 
    def flatten(new_arr = []) 
    self.each do |el| 
     if el.is_a?(Array) 
     el.flatten(new_arr) 
     else 
     new_arr << el 
     end 
    end 

    new_arr 
    end 
end 

p flatten [1, [2, 3, [4, 5, [6, 7]]]] 
#=> [1, 2, 3, 4, 5, 6, 7] 

Anche se sembra rubino non è sempre ottimizzato per la ricorsione in coda: Does ruby perform tail call optimization?