Mappa un array modificando solo gli elementi che corrispondono a una determinata condizione

StackOverflow https://stackoverflow.com/questions/614335

  •  03-07-2019
  •  | 
  •  

Domanda

In Ruby, qual è il modo più espressivo per mappare un array in modo tale che alcuni elementi vengano modificati e gli altri non vengano toccati ?

Questo è un modo semplice per farlo:

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

Omettendo il "solo" " caso ovviamente se non abbastanza:

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

Quello che vorrei è qualcosa del genere:

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

C'è un modo carino per farlo in Ruby (o forse Rails ha un qualche metodo di praticità che non ho ancora trovato)?


Grazie a tutti per la risposta. Mentre mi hai convinto collettivamente che è meglio usare map con l'operatore ternario, alcuni di voi hanno pubblicato risposte molto interessanti!

È stato utile?

Soluzione

Sono d'accordo che l'istruzione della mappa sia buona così com'è. È chiaro e semplice ,, e sarebbe facile che chiunque possa mantenere.

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"

Altri suggerimenti

Poiché le matrici sono puntatori, 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, assicurati di utilizzare un metodo che modifica l'oggetto anziché creare un nuovo oggetto. Per esempio. upcase! rispetto a upcase .

La procedura esatta dipende da cosa stai esattamente cercando di ottenere. È difficile ottenere una risposta definitiva con esempi di foo-bar.

old_a.map! { |a| a == "b" ? a + "!" : a }

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

map! modifica il ricevitore in posizione, quindi old_a è ora l'array restituito.

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'uso di map è più pulito, più conciso e non richiede più blocchi.

One liner:

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

Nel codice sopra, inizi con [] " cumulativo " ;. Come si enumera attraverso un Enumeratore (nel nostro caso l'array, ["quot" a "quot", "quot", "quot" c)], cumulativo e "l'attuale" l'oggetto viene passato al nostro blocco (| cumulativo, i |) e il risultato dell'esecuzione del nostro blocco viene assegnato a cumulativo. Quello che faccio sopra è mantenere invariato l'accumulo quando l'articolo non è " b " e aggiungi " b! " nell'array cumulativo e restituirlo quando è a b.

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

Puoi combinare select con map per ottenere ciò che stai cercando:

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

All'interno del blocco select , specifichi le condizioni affinché un elemento sia "selezionato". Questo restituirà un array. Puoi chiamare " map " sull'array risultante per aggiungere il punto esclamativo ad esso.

Se non hai bisogno del vecchio array, preferisco la mappa! in questo caso perché puoi usare il! il metodo per rappresentare la modifica dell'array in atto.

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

Preferisco questo su:

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

È lungo poche righe, ma ecco un'alternativa per l'inferno:

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"]

La raccolta con ternary sembra la migliore secondo me.

Ho scoperto che il modo migliore per farlo è usare tap

arr = [1,2,3,4,5,6]
[].tap do |a|
  arr.each { |x| a << x if x%2==0 }
end
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top