2009-03-05 14 views
17

In Ruby, qual è il modo più espressivo per mappare un array in modo tale che alcuni elementi vengano modificati e gli altri rimasti intatti?Mappare un array modificando solo gli elementi che corrispondono a una determinata condizione

questo è un modo straight-forward per farlo:

old_a = ["a", "b", "c"]       # ["a", "b", "c"] 
new_a = old_a.map { |x| (x=="b" ? x+"!" : x) } # ["a", "b!", "c"] 

Tralasciando il caso "leave-alone", naturalmente, se non basta:

new_a = old_a.map { |x| x+"!" if x=="b" }  # [nil, "b!", nil] 

Quello che vorrei è qualcosa di simile questo:

new_a = old_a.map_modifying_only_elements_where (Proc.new {|x| x == "b"}) 
     do |y| 
      y + "!" 
     end 
# ["a", "b!", "c"] 

c'è qualche bel modo per fare questo in Ruby (o forse Rails ha un qualche tipo di metodo comodo che non ho ancora trovato)?


Grazie a tutti per aver risposto. Mentre tu mi hai convinto collettivamente che è meglio usare solo map con l'operatore ternario, alcuni di voi hanno postato risposte molto interessanti!

+0

Penso che la cosa #map sia valida così com'è. ;-) –

+0

Sì, sono d'accordo. Puoi togliere i paren se questo ti fa piacere di più! –

+0

Chiuso? FTL. Guarda il mio post = P –

risposta

6

Accetto che l'istruzione della mappa sia valida così com'è. È chiaro e semplice, e sarebbe facile da mantenere da parte di chiunque fosse .

Se vuoi qualcosa di più complesso, che ne dici di questo?

module Enumerable 
    def enum_filter(&filter) 
    FilteredEnumerator.new(self, &filter) 
    end 
    alias :on :enum_filter 
    class FilteredEnumerator 
    include Enumerable 
    def initialize(enum, &filter) 
     @enum, @filter = enum, filter 
     if enum.respond_to?(:map!) 
     def self.map! 
      @enum.map! { |elt| @filter[elt] ? yield(elt) : elt } 
     end 
     end 
    end 
    def each 
     @enum.each { |elt| yield(elt) if @filter[elt] } 
    end 
    def each_with_index 
     @enum.each_with_index { |elt,index| yield(elt, index) if @filter[elt] } 
    end 
    def map 
     @enum.map { |elt| @filter[elt] ? yield(elt) : elt } 
    end 
    alias :and :enum_filter 
    def or 
     FilteredEnumerator.new(@enum) { |elt| @filter[elt] || yield(elt) } 
    end 
    end 
end 

%w{ a b c }.on { |x| x == 'b' }.map { |x| x + "!" } #=> [ 'a', 'b!', 'c' ] 

require 'set' 
Set.new(%w{ He likes dogs}).on { |x| x.length % 2 == 0 }.map! { |x| x.reverse } #=> #<Set: {"likes", "eH", "sgod"}> 

('a'..'z').on { |x| x[0] % 6 == 0 }.or { |x| 'aeiouy'[x] }.to_a.join #=> "aefiloruxy" 
+16

You ho trasformato una linea in trent'anni. Mi piace il tuo stile. – Pesto

+0

Io * ho * affermato che attaccare il condizionale w/nella mappa era la cosa migliore :) – rampion

3

La tua soluzione map è la migliore. Non sono sicuro del motivo per cui pensi che map_modifying_only_elements_where sia in qualche modo migliore. L'utilizzo di map è più pulito, più conciso e non richiede più blocchi.

1

Se non è necessario il vecchio array, preferisco la mappa! in questo caso perché puoi usare il! metodo per rappresentare che stai cambiando la matrice sul posto.

self.answers.map!{ |x| (x=="b" ? x+"!" : x) } 

Preferisco questo oltre:

new_map = self.old_map{ |x| (x=="b" ? x+"!" : x) } 
7
old_a.map! { |a| a == "b" ? a + "!" : a } 

=> ["a", "b!", "c"] 

map! modifica il ricevitore agganciato, in modo old_a è ora che matrice restituita.

1

È lunga poche righe, ma ecco un'alternativa per il gusto di farlo:

oa = %w| a b c | 
na = oa.partition { |a| a == 'b' } 
na.first.collect! { |a| a+'!' } 
na.flatten! #Add .sort! here if you wish 
p na 
# >> ["b!", "a", "c"] 

colletta con ternario sembra migliore a mio parere.

23

Poiché gli array sono puntatori, questo funziona anche:

a = ["hello", "to", "you", "dude"] 
a.select {|i| i.length <= 3 }.each {|i| i << "!" } 

puts a.inspect 
# => ["hello", "to!", "you!", "dude"] 

Nel ciclo, assicurarsi di utilizzare un metodo che altera l'oggetto piuttosto che creare un nuovo oggetto. Per esempio. upcase! rispetto a upcase.

La procedura esatta dipende da cosa esattamente si sta cercando di raggiungere. È difficile trovare una risposta definitiva con esempi di foo-bar.

1

Ho trovato che il modo migliore per raggiungere questo obiettivo è quello di utilizzare tap

arr = [1,2,3,4,5,6] 
[].tap do |a| 
    arr.each { |x| a << x if x%2==0 } 
end 
2

uno di linea:

["a", "b", "c"].inject([]) { |cumulative, i| i == "b" ? (cumulative << "#{i}!") : cumulative } 

Nel codice di cui sopra, si inizia con [] "cumulativo". Come si enumera attraverso un Enumeratore (nel nostro caso l'array, ["a", "b", "c"]), l'elemento cumulativo e "corrente" viene passato al nostro blocco (| cumulativo, i |) e il risultato dell'esecuzione del nostro blocco è assegnato a cumulativo. Quello che faccio sopra è mantenere il totale invariato quando l'oggetto non è "b" e aggiungere "b!" alla matrice cumulativa e restituirla quando è a b.

C'è una risposta sopra che utilizza select, che è il modo più semplice per farlo (e ricordarlo).

È possibile combinare select con map al fine di ottenere quello che stai cercando:

arr = ["a", "b", "c"].select { |i| i == "b" }.map { |i| "#{i}!" } 
=> ["b!"] 

All'interno del blocco select, si specificano le condizioni per un elemento da "selezionati". Questo restituirà un array. Puoi chiamare "map" sull'array risultante per aggiungere il punto esclamativo ad esso.