Mapper un tableau en modifiant uniquement les éléments correspondant à une certaine condition

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

  •  03-07-2019
  •  | 
  •  

Question

En Ruby, quel est le moyen le plus expressif de mapper un tableau de telle sorte que certains éléments soient modifiés et les autres intacts ?

C’est une façon simple de le faire:

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

Omettre le "laisser-seul" " cas bien sûr si pas assez:

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

Ce que j'aimerais, c'est quelque chose comme ça:

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

Existe-t-il un moyen intéressant de faire cela en Ruby (ou peut-être que Rails propose une méthode de commodité que je n’ai pas encore trouvée)?

Merci à tous d'avoir répondu. Si vous m'avez collectivement convaincu qu'il était préférable d'utiliser map avec l'opérateur ternaire, certains d'entre vous ont publié des réponses très intéressantes!

Était-ce utile?

La solution

Je conviens que la déclaration sur la carte est bonne telle quelle. C'est clair et simple ,, et facile à maintenir.

Si vous voulez quelque chose de plus complexe, que diriez-vous de cela?

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"

Autres conseils

Les tableaux étant des pointeurs, cela fonctionne également:

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

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

Dans la boucle, assurez-vous d'utiliser une méthode qui modifie l'objet au lieu de créer un nouvel objet. Par exemple. upcase! par rapport à upcase .

La procédure exacte dépend de ce que vous essayez exactement d’atteindre. Il est difficile de trouver une réponse définitive avec des exemples de barres de sécurité.

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

donne

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

map! modifie le récepteur en place, donc old_a est maintenant ce tableau retourné.

Votre solution map est la meilleure. Je ne sais pas pourquoi vous pensez que map_modifying_only_elements_where est meilleur en quelque sorte. Utiliser map est plus propre, plus concis et ne nécessite pas plusieurs blocs.

Une doublure:

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

Dans le code ci-dessus, vous commencez par [] "cumulative". Lorsque vous énumérez un énumérateur (dans notre cas, le tableau, "" a "," b "," c "]), cumulatif ainsi que" l'actuel ". item est passé à notre bloc (| cumulative, i |) et le résultat de l'exécution de notre bloc est assigné à cumulative. Ce que je fais ci-dessus est de garder le cumulatif inchangé lorsque l'élément n'est pas "b". et ajoutez " b! " à un tableau cumulatif et le retourner quand il est un b.

Il existe une réponse ci-dessus qui utilise select , ce qui est le moyen le plus simple de le faire (et de s'en souvenir).

Vous pouvez combiner select avec map afin de réaliser ce que vous recherchez:

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

Dans le bloc select , vous spécifiez les conditions pour qu'un élément soit "sélectionné". Cela retournera un tableau. Vous pouvez appeler " map " sur le tableau résultant pour y ajouter le point d’exclamation.

Si vous n'avez pas besoin de l'ancien tableau, je préfère la carte! dans ce cas parce que vous pouvez utiliser le! méthode pour vous représenter changez le tableau en place.

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

Je préfère ceci par-dessus:

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

Cela fait quelques lignes, mais voici une solution de rechange:

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

À mon avis, la collecte avec ternaire semble préférable.

J'ai constaté que le meilleur moyen d'y parvenir consiste à utiliser tap

.
arr = [1,2,3,4,5,6]
[].tap do |a|
  arr.each { |x| a << x if x%2==0 }
end
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top