Mapper un tableau en modifiant uniquement les éléments correspondant à une certaine condition
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!
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