Mapear uma matriz modificando somente os elementos que correspondem a um determinado estado
Pergunta
Em Ruby, que é a forma mais expressiva para mapear um conjunto de tal forma que certos elementos são modificados e os outros deixado intocado ?
Esta é uma maneira simples e direta para fazê-lo:
old_a = ["a", "b", "c"] # ["a", "b", "c"]
new_a = old_a.map { |x| (x=="b" ? x+"!" : x) } # ["a", "b!", "c"]
A omissão do caso "licença-alone", claro, se não o suficiente:
new_a = old_a.map { |x| x+"!" if x=="b" } # [nil, "b!", nil]
O que eu gostaria é algo como isto:
new_a = old_a.map_modifying_only_elements_where (Proc.new {|x| x == "b"})
do |y|
y + "!"
end
# ["a", "b!", "c"]
Existe alguma boa maneira de fazer isso em Ruby (ou talvez Rails tem algum tipo de método de conveniência que eu não encontrei ainda)?
todo mundo Obrigado por responder. Enquanto você me convenceu coletivamente que é melhor map
uso apenas com o operador ternário, alguns de vocês postou respostas muito interessantes!
Solução
Eu concordo que o mapa declaração é bom como ele é. É claro e simples ,, e seria fácil para qualquer um para manter.
Se você quiser algo mais complexo, como sobre isso?
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"
Outras dicas
Como os arrays são ponteiros, isso também funciona:
a = ["hello", "to", "you", "dude"]
a.select {|i| i.length <= 3 }.each {|i| i << "!" }
puts a.inspect
# => ["hello", "to!", "you!", "dude"]
No loop, certifique-se de usar um método que altera o objeto em vez de criar um novo objeto. Por exemplo. upcase!
comparação com upcase
.
O procedimento exato depende do que exatamente você está tentando alcançar. É difícil pregar uma resposta definitiva com exemplos foo-bar.
old_a.map! { |a| a == "b" ? a + "!" : a }
dá
=> ["a", "b!", "c"]
modifica map!
o receptor no lugar, então old_a
é agora que array retornado.
A sua solução map
é o melhor. Eu não sei por que você acha map_modifying_only_elements_where é de alguma forma melhor. Usando map
é mais limpo, mais conciso, e não necessita de vários blocos.
Um liner:
["a", "b", "c"].inject([]) { |cumulative, i| i == "b" ? (cumulative << "#{i}!") : cumulative }
No código acima, você começa com [] "cumulativo". Como você enumerar através de uma Enumerator (em nosso caso, o array, [ "a", "b", "c"]), cumulativos, bem como "a corrente" item de conseguir passar para o nosso bloco (| cumulativo, i |) e o resultado da execução de nosso bloco é atribuído a cumulativo. O que eu faço acima é manter cumulativo inalterado quando o item não é "b" e acrescentar "b!" a variedade acumulado e devolvê-lo quando é uma b.
Há uma resposta acima, que usa select
, que é a maneira mais fácil de fazer (e lembre-se)-lo.
Você pode combinar select
com map
, a fim de conseguir o que você está procurando:
arr = ["a", "b", "c"].select { |i| i == "b" }.map { |i| "#{i}!" }
=> ["b!"]
Dentro do bloco select
, você especifica as condições para um elemento a ser "escolhidos". Isso irá retornar um array. Você pode chamar "mapa" sobre a matriz resultante para anexar o ponto de exclamação a ele.
Se você não precisa a matriz velha, eu prefiro mapa! neste caso, porque você pode usar o! método para representá-lo estão mudando a matriz no local.
self.answers.map!{ |x| (x=="b" ? x+"!" : x) }
Eu prefiro este ao longo:
new_map = self.old_map{ |x| (x=="b" ? x+"!" : x) }
É poucas linhas de comprimento, mas aqui está uma alternativa para o inferno dele:
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"]
A coleta com ternário parece melhor na minha opinião.
Eu descobri que a melhor maneira de conseguir isso é através da utilização tap
arr = [1,2,3,4,5,6]
[].tap do |a|
arr.each { |x| a << x if x%2==0 }
end