Mapear uma matriz modificando somente os elementos que correspondem a um determinado estado

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

  •  03-07-2019
  •  | 
  •  

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!

Foi útil?

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 }

=> ["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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top